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