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