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