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