View Javadoc
1   package org.argeo.osgi.useradmin;
2   
3   import static org.argeo.naming.LdapAttrs.objectClass;
4   import static org.argeo.naming.LdapObjs.inetOrgPerson;
5   
6   import java.io.File;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.io.OutputStream;
11  import java.net.URI;
12  import java.util.ArrayList;
13  import java.util.Collections;
14  import java.util.Dictionary;
15  import java.util.HashSet;
16  import java.util.Hashtable;
17  import java.util.List;
18  import java.util.Set;
19  import java.util.SortedMap;
20  import java.util.TreeMap;
21  
22  import javax.naming.NameNotFoundException;
23  import javax.naming.NamingEnumeration;
24  import javax.naming.directory.Attributes;
25  import javax.naming.ldap.LdapName;
26  import javax.transaction.TransactionManager;
27  
28  import org.argeo.naming.LdifParser;
29  import org.argeo.naming.LdifWriter;
30  import org.osgi.framework.Filter;
31  import org.osgi.service.useradmin.Role;
32  import org.osgi.service.useradmin.User;
33  
34  /**
35   * A user admin based on a LDIF files. Requires a {@link TransactionManager} and
36   * an open transaction for write access.
37   */
38  public class LdifUserAdmin extends AbstractUserDirectory {
39  	private SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
40  	private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
41  
42  	public LdifUserAdmin(String uri, String baseDn) {
43  		this(fromUri(uri, baseDn));
44  	}
45  
46  	public LdifUserAdmin(Dictionary<String, ?> properties) {
47  		super(null, properties);
48  	}
49  
50  	public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
51  		super(uri, properties);
52  	}
53  
54  	@Override
55  	protected AbstractUserDirectory scope(User user) {
56  		Dictionary<String, Object> credentials = user.getCredentials();
57  		String username = (String) credentials.get(SHARED_STATE_USERNAME);
58  		if (username == null)
59  			username = user.getName();
60  		Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
61  		byte[] pwd = (byte[]) pwdCred;
62  		if (pwd != null) {
63  			char[] password = DigestUtils.bytesToChars(pwd);
64  			User directoryUser = (User) getRole(username);
65  			if (!directoryUser.hasCredential(null, password))
66  				throw new UserDirectoryException("Invalid credentials");
67  		} else {
68  			throw new UserDirectoryException("Password is required");
69  		}
70  		Dictionary<String, Object> properties = cloneProperties();
71  		properties.put(UserAdminConf.readOnly.name(), "true");
72  		LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties);
73  		scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups);
74  		scopedUserAdmin.users = Collections.unmodifiableSortedMap(users);
75  		return scopedUserAdmin;
76  	}
77  
78  	private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
79  		Hashtable<String, Object> res = new Hashtable<String, Object>();
80  		res.put(UserAdminConf.uri.name(), uri);
81  		res.put(UserAdminConf.baseDn.name(), baseDn);
82  		return res;
83  	}
84  
85  	public void init() {
86  		try {
87  			if (getUri().getScheme().equals("file")) {
88  				File file = new File(getUri());
89  				if (!file.exists())
90  					return;
91  			}
92  			load(getUri().toURL().openStream());
93  		} catch (Exception e) {
94  			throw new UserDirectoryException("Cannot open URL " + getUri(), e);
95  		}
96  	}
97  
98  	public void save() {
99  		if (getUri() == null)
100 			throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set");
101 		if (isReadOnly())
102 			throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only");
103 		try (FileOutputStream out = new FileOutputStream(new File(getUri()))) {
104 			save(out);
105 		} catch (IOException e) {
106 			throw new UserDirectoryException("Cannot save user admin to " + getUri(), e);
107 		}
108 	}
109 
110 	public void save(OutputStream out) throws IOException {
111 		try {
112 			LdifWriter ldifWriter = new LdifWriter(out);
113 			for (LdapName name : groups.keySet())
114 				ldifWriter.writeEntry(name, groups.get(name).getAttributes());
115 			for (LdapName name : users.keySet())
116 				ldifWriter.writeEntry(name, users.get(name).getAttributes());
117 		} finally {
118 			out.close();
119 		}
120 	}
121 
122 	protected void load(InputStream in) {
123 		try {
124 			users.clear();
125 			groups.clear();
126 
127 			LdifParser ldifParser = new LdifParser();
128 			SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
129 			for (LdapName key : allEntries.keySet()) {
130 				Attributes attributes = allEntries.get(key);
131 				// check for inconsistency
132 				Set<String> lowerCase = new HashSet<String>();
133 				NamingEnumeration<String> ids = attributes.getIDs();
134 				while (ids.hasMoreElements()) {
135 					String id = ids.nextElement().toLowerCase();
136 					if (lowerCase.contains(id))
137 						throw new UserDirectoryException(key + " has duplicate id " + id);
138 					lowerCase.add(id);
139 				}
140 
141 				// analyse object classes
142 				NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
143 				// System.out.println(key);
144 				objectClasses: while (objectClasses.hasMore()) {
145 					String objectClass = objectClasses.next().toString();
146 					// System.out.println(" " + objectClass);
147 					if (objectClass.equals(inetOrgPerson.name())) {
148 						users.put(key, new LdifUser(this, key, attributes));
149 						break objectClasses;
150 					} else if (objectClass.equals(getGroupObjectClass())) {
151 						groups.put(key, new LdifGroup(this, key, attributes));
152 						break objectClasses;
153 					}
154 				}
155 			}
156 		} catch (Exception e) {
157 			throw new UserDirectoryException("Cannot load user admin service from LDIF", e);
158 		}
159 	}
160 
161 	public void destroy() {
162 		if (users == null || groups == null)
163 			throw new UserDirectoryException("User directory " + getBaseDn() + " is already destroyed");
164 		users = null;
165 		groups = null;
166 	}
167 
168 	@Override
169 	protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
170 		if (groups.containsKey(key))
171 			return groups.get(key);
172 		if (users.containsKey(key))
173 			return users.get(key);
174 		throw new NameNotFoundException(key + " not persisted");
175 	}
176 
177 	@Override
178 	protected Boolean daoHasRole(LdapName dn) {
179 		return users.containsKey(dn) || groups.containsKey(dn);
180 	}
181 
182 	protected List<DirectoryUser> doGetRoles(Filter f) {
183 		ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
184 		if (f == null) {
185 			res.addAll(users.values());
186 			res.addAll(groups.values());
187 		} else {
188 			for (DirectoryUser user : users.values()) {
189 				if (f.match(user.getProperties()))
190 					res.add(user);
191 			}
192 			for (DirectoryUser group : groups.values())
193 				if (f.match(group.getProperties()))
194 					res.add(group);
195 		}
196 		return res;
197 	}
198 
199 	@Override
200 	protected List<LdapName> getDirectGroups(LdapName dn) {
201 		List<LdapName> directGroups = new ArrayList<LdapName>();
202 		for (LdapName name : groups.keySet()) {
203 			DirectoryGroup group = groups.get(name);
204 			if (group.getMemberNames().contains(dn))
205 				directGroups.add(group.getDn());
206 		}
207 		return directGroups;
208 	}
209 
210 	@Override
211 	protected void prepare(UserDirectoryWorkingCopy wc) {
212 		// delete
213 		for (LdapName dn : wc.getDeletedUsers().keySet()) {
214 			if (users.containsKey(dn))
215 				users.remove(dn);
216 			else if (groups.containsKey(dn))
217 				groups.remove(dn);
218 			else
219 				throw new UserDirectoryException("User to delete not found " + dn);
220 		}
221 		// add
222 		for (LdapName dn : wc.getNewUsers().keySet()) {
223 			DirectoryUser user = wc.getNewUsers().get(dn);
224 			if (users.containsKey(dn) || groups.containsKey(dn))
225 				throw new UserDirectoryException("User to create found " + dn);
226 			else if (Role.USER == user.getType())
227 				users.put(dn, user);
228 			else if (Role.GROUP == user.getType())
229 				groups.put(dn, (DirectoryGroup) user);
230 			else
231 				throw new UserDirectoryException("Unsupported role type " + user.getType() + " for new user " + dn);
232 		}
233 		// modify
234 		for (LdapName dn : wc.getModifiedUsers().keySet()) {
235 			Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
236 			DirectoryUser user;
237 			if (users.containsKey(dn))
238 				user = users.get(dn);
239 			else if (groups.containsKey(dn))
240 				user = groups.get(dn);
241 			else
242 				throw new UserDirectoryException("User to modify no found " + dn);
243 			user.publishAttributes(modifiedAttrs);
244 		}
245 	}
246 
247 	@Override
248 	protected void commit(UserDirectoryWorkingCopy wc) {
249 		save();
250 	}
251 
252 	@Override
253 	protected void rollback(UserDirectoryWorkingCopy wc) {
254 		init();
255 	}
256 
257 }