]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
Give back DataAdmin rights to members of the admin group
[lgpl/argeo-commons.git] / org.argeo.enterprise / src / org / argeo / osgi / useradmin / AbstractUserDirectory.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 import static org.argeo.naming.LdapObjs.organizationalPerson;
6 import static org.argeo.naming.LdapObjs.person;
7 import static org.argeo.naming.LdapObjs.top;
8
9 import java.io.File;
10 import java.net.URI;
11 import java.net.URISyntaxException;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Dictionary;
15 import java.util.Enumeration;
16 import java.util.Hashtable;
17 import java.util.Iterator;
18 import java.util.List;
19
20 import javax.naming.InvalidNameException;
21 import javax.naming.directory.Attributes;
22 import javax.naming.directory.BasicAttribute;
23 import javax.naming.directory.BasicAttributes;
24 import javax.naming.ldap.LdapName;
25 import javax.naming.ldap.Rdn;
26 import javax.transaction.SystemException;
27 import javax.transaction.Transaction;
28 import javax.transaction.TransactionManager;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.argeo.naming.LdapAttrs;
33 import org.osgi.framework.Filter;
34 import org.osgi.framework.FrameworkUtil;
35 import org.osgi.framework.InvalidSyntaxException;
36 import org.osgi.service.useradmin.Authorization;
37 import org.osgi.service.useradmin.Role;
38 import org.osgi.service.useradmin.User;
39 import org.osgi.service.useradmin.UserAdmin;
40
41 /** Base class for a {@link UserDirectory}. */
42 public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
43 static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
44 static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
45
46 private final static Log log = LogFactory.getLog(AbstractUserDirectory.class);
47
48 private final Hashtable<String, Object> properties;
49 private final LdapName baseDn, userBaseDn, groupBaseDn;
50 private final String userObjectClass, userBase, groupObjectClass, groupBase;
51
52 private final boolean readOnly;
53 private final URI uri;
54
55 private UserAdmin externalRoles;
56 private List<String> indexedUserProperties = Arrays
57 .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(), LdapAttrs.cn.name() });
58
59 private String memberAttributeId = "member";
60 private List<String> credentialAttributeIds = Arrays.asList(new String[] { LdapAttrs.userPassword.name() });
61
62 // JTA
63 private TransactionManager transactionManager;
64 private WcXaResource xaResource = new WcXaResource(this);
65
66 public AbstractUserDirectory(Dictionary<String, ?> props) {
67 properties = new Hashtable<String, Object>();
68 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
69 String key = keys.nextElement();
70 properties.put(key, props.get(key));
71 }
72
73 String uriStr = UserAdminConf.uri.getValue(properties);
74 if (uriStr == null)
75 uri = null;
76 else
77 try {
78 uri = new URI(uriStr);
79 } catch (URISyntaxException e) {
80 throw new UserDirectoryException("Badly formatted URI " + uriStr, e);
81 }
82
83 userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
84 userBase = UserAdminConf.userBase.getValue(properties);
85 groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
86 groupBase = UserAdminConf.groupBase.getValue(properties);
87 try {
88 baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
89 userBaseDn = new LdapName(userBase + "," + baseDn);
90 groupBaseDn = new LdapName(groupBase + "," + baseDn);
91 } catch (InvalidNameException e) {
92 throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
93 }
94 String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
95 if (readOnlyStr == null) {
96 readOnly = readOnlyDefault(uri);
97 properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
98 } else
99 readOnly = new Boolean(readOnlyStr);
100 }
101
102 /** Returns the groups this user is a direct member of. */
103 protected abstract List<LdapName> getDirectGroups(LdapName dn);
104
105 protected abstract Boolean daoHasRole(LdapName dn);
106
107 protected abstract DirectoryUser daoGetRole(LdapName key);
108
109 protected abstract List<DirectoryUser> doGetRoles(Filter f);
110
111 protected abstract AbstractUserDirectory scope(User user);
112
113 public void init() {
114
115 }
116
117 public void destroy() {
118
119 }
120
121 protected boolean isEditing() {
122 return xaResource.wc() != null;
123 }
124
125 protected UserDirectoryWorkingCopy getWorkingCopy() {
126 UserDirectoryWorkingCopy wc = xaResource.wc();
127 if (wc == null)
128 return null;
129 return wc;
130 }
131
132 protected void checkEdit() {
133 Transaction transaction;
134 try {
135 transaction = transactionManager.getTransaction();
136 } catch (SystemException e) {
137 throw new UserDirectoryException("Cannot get transaction", e);
138 }
139 if (transaction == null)
140 throw new UserDirectoryException("A transaction needs to be active in order to edit");
141 if (xaResource.wc() == null) {
142 try {
143 transaction.enlistResource(xaResource);
144 } catch (Exception e) {
145 throw new UserDirectoryException("Cannot enlist " + xaResource, e);
146 }
147 } else {
148 }
149 }
150
151 protected List<Role> getAllRoles(DirectoryUser user) {
152 List<Role> allRoles = new ArrayList<Role>();
153 if (user != null) {
154 collectRoles(user, allRoles);
155 allRoles.add(user);
156 } else
157 collectAnonymousRoles(allRoles);
158 return allRoles;
159 }
160
161 private void collectRoles(DirectoryUser user, List<Role> allRoles) {
162 for (LdapName groupDn : getDirectGroups(user.getDn())) {
163 // TODO check for loops
164 DirectoryUser group = doGetRole(groupDn);
165 allRoles.add(group);
166 collectRoles(group, allRoles);
167 }
168 }
169
170 private void collectAnonymousRoles(List<Role> allRoles) {
171 // TODO gather anonymous roles
172 }
173
174 // USER ADMIN
175 @Override
176 public Role getRole(String name) {
177 return doGetRole(toDn(name));
178 }
179
180 protected DirectoryUser doGetRole(LdapName dn) {
181 UserDirectoryWorkingCopy wc = getWorkingCopy();
182 DirectoryUser user = daoGetRole(dn);
183 if (wc != null) {
184 if (user == null && wc.getNewUsers().containsKey(dn))
185 user = wc.getNewUsers().get(dn);
186 else if (wc.getDeletedUsers().containsKey(dn))
187 user = null;
188 }
189 return user;
190 }
191
192 @SuppressWarnings("unchecked")
193 @Override
194 public Role[] getRoles(String filter) throws InvalidSyntaxException {
195 UserDirectoryWorkingCopy wc = getWorkingCopy();
196 Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
197 List<DirectoryUser> res = doGetRoles(f);
198 if (wc != null) {
199 for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
200 DirectoryUser user = it.next();
201 LdapName dn = user.getDn();
202 if (wc.getDeletedUsers().containsKey(dn))
203 it.remove();
204 }
205 for (DirectoryUser user : wc.getNewUsers().values()) {
206 if (f == null || f.match(user.getProperties()))
207 res.add(user);
208 }
209 // no need to check modified users,
210 // since doGetRoles was already based on the modified attributes
211 }
212 return res.toArray(new Role[res.size()]);
213 }
214
215 @Override
216 public User getUser(String key, String value) {
217 // TODO check value null or empty
218 List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>(getIndexedUserProperties().size());
219 if (key != null) {
220 doGetUser(key, value, collectedUsers);
221 } else {
222 // try dn
223 DirectoryUser user = null;
224 try {
225 user = (DirectoryUser) getRole(value);
226 if (user != null)
227 collectedUsers.add(user);
228 } catch (Exception e) {
229 // silent
230 }
231 // try all indexes
232 for (String attr : getIndexedUserProperties())
233 doGetUser(attr, value, collectedUsers);
234 }
235 if (collectedUsers.size() == 1)
236 return collectedUsers.get(0);
237 else if (collectedUsers.size() > 1)
238 log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" : "") + value);
239 return null;
240 }
241
242 protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
243 try {
244 Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
245 List<DirectoryUser> users = doGetRoles(f);
246 collectedUsers.addAll(users);
247 } catch (InvalidSyntaxException e) {
248 throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
249 }
250 }
251
252 @Override
253 public Authorization getAuthorization(User user) {
254 if (user == null || user instanceof DirectoryUser) {
255 return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
256 } else {
257 // bind
258 AbstractUserDirectory scopedUserAdmin = scope(user);
259 DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
260 LdifAuthorization authorization = new LdifAuthorization(directoryUser,
261 scopedUserAdmin.getAllRoles(directoryUser));
262 scopedUserAdmin.destroy();
263 return authorization;
264 }
265 }
266
267 @Override
268 public Role createRole(String name, int type) {
269 checkEdit();
270 UserDirectoryWorkingCopy wc = getWorkingCopy();
271 LdapName dn = toDn(name);
272 if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
273 throw new UserDirectoryException("Already a role " + name);
274 BasicAttributes attrs = new BasicAttributes(true);
275 // attrs.put(LdifName.dn.name(), dn.toString());
276 Rdn nameRdn = dn.getRdn(dn.size() - 1);
277 // TODO deal with multiple attr RDN
278 attrs.put(nameRdn.getType(), nameRdn.getValue());
279 if (wc.getDeletedUsers().containsKey(dn)) {
280 wc.getDeletedUsers().remove(dn);
281 wc.getModifiedUsers().put(dn, attrs);
282 } else {
283 wc.getModifiedUsers().put(dn, attrs);
284 DirectoryUser newRole = newRole(dn, type, attrs);
285 wc.getNewUsers().put(dn, newRole);
286 }
287 return getRole(name);
288 }
289
290 protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
291 LdifUser newRole;
292 BasicAttribute objClass = new BasicAttribute(objectClass.name());
293 if (type == Role.USER) {
294 String userObjClass = newUserObjectClass(dn);
295 objClass.add(userObjClass);
296 if (inetOrgPerson.name().equals(userObjClass)) {
297 objClass.add(organizationalPerson.name());
298 objClass.add(person.name());
299 } else if (organizationalPerson.name().equals(userObjClass)) {
300 objClass.add(person.name());
301 }
302 objClass.add(top.name());
303 attrs.put(objClass);
304 newRole = new LdifUser(this, dn, attrs);
305 } else if (type == Role.GROUP) {
306 String groupObjClass = getGroupObjectClass();
307 objClass.add(groupObjClass);
308 // objClass.add(LdifName.extensibleObject.name());
309 objClass.add(top.name());
310 attrs.put(objClass);
311 newRole = new LdifGroup(this, dn, attrs);
312 } else
313 throw new UserDirectoryException("Unsupported type " + type);
314 return newRole;
315 }
316
317 @Override
318 public boolean removeRole(String name) {
319 checkEdit();
320 UserDirectoryWorkingCopy wc = getWorkingCopy();
321 LdapName dn = toDn(name);
322 boolean actuallyDeleted;
323 if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
324 DirectoryUser user = (DirectoryUser) getRole(name);
325 wc.getDeletedUsers().put(dn, user);
326 actuallyDeleted = true;
327 } else {// just removing from groups (e.g. system roles)
328 actuallyDeleted = false;
329 }
330 for (LdapName groupDn : getDirectGroups(dn)) {
331 DirectoryUser group = doGetRole(groupDn);
332 group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
333 }
334 return actuallyDeleted;
335 }
336
337 // TRANSACTION
338 protected void prepare(UserDirectoryWorkingCopy wc) {
339
340 }
341
342 protected void commit(UserDirectoryWorkingCopy wc) {
343
344 }
345
346 protected void rollback(UserDirectoryWorkingCopy wc) {
347
348 }
349
350 // UTILITIES
351 protected LdapName toDn(String name) {
352 try {
353 return new LdapName(name);
354 } catch (InvalidNameException e) {
355 throw new UserDirectoryException("Badly formatted name", e);
356 }
357 }
358
359 // GETTERS
360 protected String getMemberAttributeId() {
361 return memberAttributeId;
362 }
363
364 protected List<String> getCredentialAttributeIds() {
365 return credentialAttributeIds;
366 }
367
368 protected URI getUri() {
369 return uri;
370 }
371
372 protected List<String> getIndexedUserProperties() {
373 return indexedUserProperties;
374 }
375
376 protected void setIndexedUserProperties(List<String> indexedUserProperties) {
377 this.indexedUserProperties = indexedUserProperties;
378 }
379
380 private static boolean readOnlyDefault(URI uri) {
381 if (uri == null)
382 return true;
383 if (uri.getScheme().equals("file")) {
384 File file = new File(uri);
385 if (file.exists())
386 return !file.canWrite();
387 else
388 return !file.getParentFile().canWrite();
389 }
390 return true;
391 }
392
393 public boolean isReadOnly() {
394 return readOnly;
395 }
396
397 protected UserAdmin getExternalRoles() {
398 return externalRoles;
399 }
400
401 protected int roleType(LdapName dn) {
402 if (dn.startsWith(groupBaseDn))
403 return Role.GROUP;
404 else if (dn.startsWith(userBaseDn))
405 return Role.USER;
406 else
407 return Role.GROUP;
408 }
409
410 /** dn can be null, in that case a default should be returned. */
411 public String getUserObjectClass() {
412 return userObjectClass;
413 }
414
415 public String getUserBase() {
416 return userBase;
417 }
418
419 protected String newUserObjectClass(LdapName dn) {
420 return getUserObjectClass();
421 }
422
423 public String getGroupObjectClass() {
424 return groupObjectClass;
425 }
426
427 public String getGroupBase() {
428 return groupBase;
429 }
430
431 public LdapName getBaseDn() {
432 return (LdapName) baseDn.clone();
433 }
434
435 public Dictionary<String, Object> getProperties() {
436 return properties;
437 }
438
439 public Dictionary<String, Object> cloneProperties() {
440 return new Hashtable<>(properties);
441 }
442
443 public void setExternalRoles(UserAdmin externalRoles) {
444 this.externalRoles = externalRoles;
445 }
446
447 public void setTransactionManager(TransactionManager transactionManager) {
448 this.transactionManager = transactionManager;
449 }
450
451 public WcXaResource getXaResource() {
452 return xaResource;
453 }
454
455 }