]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java
Re-add org.argeo.cms.util.useradmin
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / NodeUserAdmin.java
1 package org.argeo.cms.internal.kernel;
2
3 import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
4 import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstanceDir;
5
6 import java.io.File;
7 import java.io.IOException;
8 import java.net.URI;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Dictionary;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.Hashtable;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18
19 import javax.jcr.Repository;
20 import javax.naming.InvalidNameException;
21 import javax.naming.ldap.LdapName;
22 import javax.transaction.TransactionManager;
23
24 import org.apache.commons.io.FileUtils;
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.argeo.cms.CmsException;
28 import org.argeo.cms.auth.AuthConstants;
29 import org.argeo.node.NodeConstants;
30 import org.argeo.osgi.useradmin.LdapUserAdmin;
31 import org.argeo.osgi.useradmin.LdifUserAdmin;
32 import org.argeo.osgi.useradmin.UserAdminConf;
33 import org.argeo.osgi.useradmin.UserDirectory;
34 import org.argeo.osgi.useradmin.UserDirectoryException;
35 import org.osgi.framework.BundleContext;
36 import org.osgi.framework.FrameworkUtil;
37 import org.osgi.framework.InvalidSyntaxException;
38 import org.osgi.framework.ServiceReference;
39 import org.osgi.service.cm.ConfigurationException;
40 import org.osgi.service.cm.ManagedService;
41 import org.osgi.service.useradmin.Authorization;
42 import org.osgi.service.useradmin.Role;
43 import org.osgi.service.useradmin.User;
44 import org.osgi.service.useradmin.UserAdmin;
45 import org.osgi.util.tracker.ServiceTracker;
46 import org.osgi.util.tracker.ServiceTrackerCustomizer;
47
48 import bitronix.tm.resource.ehcache.EhCacheXAResourceProducer;
49
50 /**
51 * Aggregates multiple {@link UserDirectory} and integrates them with this node
52 * system roles.
53 */
54 class NodeUserAdmin implements UserAdmin, ManagedService, KernelConstants {
55 private final static Log log = LogFactory.getLog(NodeUserAdmin.class);
56 final static LdapName ROLES_BASE;
57 static {
58 try {
59 ROLES_BASE = new LdapName(AuthConstants.ROLES_BASEDN);
60 } catch (InvalidNameException e) {
61 throw new UserDirectoryException("Cannot initialize " + NodeUserAdmin.class, e);
62 }
63 }
64
65 private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
66
67 // DAOs
68 private UserAdmin nodeRoles = null;
69 private Map<LdapName, UserAdmin> userAdmins = new HashMap<LdapName, UserAdmin>();
70
71 // JCR
72 // private String homeBasePath = "/home";
73 // private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH;
74 // private Repository repository;
75 // private Session adminSession;
76
77 private final String cacheName = UserDirectory.class.getName();
78
79 public NodeUserAdmin() {
80 // DAOs
81 File nodeBaseDir = new File(getOsgiInstanceDir(), DIR_NODE);
82 nodeBaseDir.mkdirs();
83 String userAdminUri = getFrameworkProp(NodeConstants.USERADMIN_URIS);
84 initUserAdmins(userAdminUri, nodeBaseDir);
85 String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
86 initNodeRoles(nodeRolesUri, nodeBaseDir);
87
88 new ServiceTracker<>(bc, TransactionManager.class, new TransactionManagerStc()).open();
89 }
90
91 @Override
92 public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
93 }
94
95 private class TransactionManagerStc implements ServiceTrackerCustomizer<TransactionManager, TransactionManager> {
96
97 @Override
98 public TransactionManager addingService(ServiceReference<TransactionManager> reference) {
99 TransactionManager transactionManager = bc.getService(reference);
100 ((UserDirectory) nodeRoles).setTransactionManager(transactionManager);
101 for (UserAdmin userAdmin : userAdmins.values()) {
102 if (userAdmin instanceof UserDirectory)
103 ((UserDirectory) userAdmin).setTransactionManager(transactionManager);
104 }
105 if (log.isDebugEnabled())
106 log.debug("Set transaction manager");
107 return transactionManager;
108 }
109
110 @Override
111 public void modifiedService(ServiceReference<TransactionManager> reference, TransactionManager service) {
112 }
113
114 @Override
115 public void removedService(ServiceReference<TransactionManager> reference, TransactionManager service) {
116 ((UserDirectory) nodeRoles).setTransactionManager(null);
117 for (UserAdmin userAdmin : userAdmins.values()) {
118 if (userAdmin instanceof UserDirectory)
119 ((UserDirectory) userAdmin).setTransactionManager(null);
120 }
121 }
122
123 }
124
125 @Deprecated
126 public NodeUserAdmin(TransactionManager transactionManager, Repository repository) {
127 // this.repository = repository;
128 // try {
129 // this.adminSession = this.repository.login();
130 // } catch (RepositoryException e) {
131 // throw new CmsException("Cannot log-in", e);
132 // }
133
134 // DAOs
135 File nodeBaseDir = new File(getOsgiInstanceDir(), DIR_NODE);
136 nodeBaseDir.mkdirs();
137 String userAdminUri = getFrameworkProp(NodeConstants.USERADMIN_URIS);
138 initUserAdmins(userAdminUri, nodeBaseDir);
139 String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
140 initNodeRoles(nodeRolesUri, nodeBaseDir);
141
142 // Transaction manager
143 ((UserDirectory) nodeRoles).setTransactionManager(transactionManager);
144 for (UserAdmin userAdmin : userAdmins.values()) {
145 if (userAdmin instanceof UserDirectory)
146 ((UserDirectory) userAdmin).setTransactionManager(transactionManager);
147 }
148
149 // JCR
150 // initJcr(adminSession);
151 }
152
153 Dictionary<String, Object> currentState() {
154 Dictionary<String, Object> res = new Hashtable<String, Object>();
155 for (LdapName name : userAdmins.keySet()) {
156 StringBuilder buf = new StringBuilder();
157 if (userAdmins.get(name) instanceof UserDirectory) {
158 UserDirectory userDirectory = (UserDirectory) userAdmins.get(name);
159 String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
160 res.put(uri, "");
161 } else {
162 buf.append('/').append(name.toString()).append("?readOnly=true");
163 }
164 }
165 return res;
166 }
167
168 public void destroy() {
169 for (LdapName name : userAdmins.keySet()) {
170 if (userAdmins.get(name) instanceof UserDirectory) {
171 UserDirectory userDirectory = (UserDirectory) userAdmins.get(name);
172 try {
173 // FIXME Make it less bitronix dependant
174 EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
175 } catch (Exception e) {
176 log.error("Cannot unregister resource from Bitronix", e);
177 }
178 userDirectory.destroy();
179
180 }
181 }
182 }
183
184 @Override
185 public Role createRole(String name, int type) {
186 return findUserAdmin(name).createRole(name, type);
187 }
188
189 @Override
190 public boolean removeRole(String name) {
191 boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
192 nodeRoles.removeRole(name);
193 return actuallyDeleted;
194 }
195
196 @Override
197 public Role getRole(String name) {
198 return findUserAdmin(name).getRole(name);
199 }
200
201 @Override
202 public Role[] getRoles(String filter) throws InvalidSyntaxException {
203 List<Role> res = new ArrayList<Role>();
204 for (UserAdmin userAdmin : userAdmins.values()) {
205 res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
206 }
207 res.addAll(Arrays.asList(nodeRoles.getRoles(filter)));
208 return res.toArray(new Role[res.size()]);
209 }
210
211 @Override
212 public User getUser(String key, String value) {
213 List<User> res = new ArrayList<User>();
214 for (UserAdmin userAdmin : userAdmins.values()) {
215 User u = userAdmin.getUser(key, value);
216 if (u != null)
217 res.add(u);
218 }
219 // Note: node roles cannot contain users, so it is not searched
220 return res.size() == 1 ? res.get(0) : null;
221 }
222
223 @Override
224 public Authorization getAuthorization(User user) {
225 if (user == null) {// anonymous
226 return nodeRoles.getAuthorization(null);
227 }
228 UserAdmin userAdmin = findUserAdmin(user.getName());
229 Authorization rawAuthorization = userAdmin.getAuthorization(user);
230 // gather system roles
231 Set<String> systemRoles = new HashSet<String>();
232 for (String role : rawAuthorization.getRoles()) {
233 Authorization auth = nodeRoles.getAuthorization((User) userAdmin.getRole(role));
234 systemRoles.addAll(Arrays.asList(auth.getRoles()));
235 }
236 Authorization authorization = new NodeAuthorization(rawAuthorization.getName(), rawAuthorization.toString(),
237 systemRoles, rawAuthorization.getRoles());
238 // syncJcr(adminSession, authorization);
239 return authorization;
240 }
241
242 //
243 // USER ADMIN AGGREGATOR
244 //
245 public void addUserAdmin(String baseDn, UserAdmin userAdmin) {
246 if (userAdmins.containsKey(baseDn))
247 throw new UserDirectoryException("There is already a user admin for " + baseDn);
248 try {
249 userAdmins.put(new LdapName(baseDn), userAdmin);
250 } catch (InvalidNameException e) {
251 throw new UserDirectoryException("Badly formatted base DN " + baseDn, e);
252 }
253 if (userAdmin instanceof UserDirectory) {
254 try {
255 // FIXME Make it less bitronix dependant
256 EhCacheXAResourceProducer.registerXAResource(cacheName, ((UserDirectory) userAdmin).getXaResource());
257 } catch (Exception e) {
258 log.error("Cannot register resource to Bitronix", e);
259 }
260 }
261 }
262
263 private UserAdmin findUserAdmin(String name) {
264 try {
265 return findUserAdmin(new LdapName(name));
266 } catch (InvalidNameException e) {
267 throw new UserDirectoryException("Badly formatted name " + name, e);
268 }
269 }
270
271 private UserAdmin findUserAdmin(LdapName name) {
272 if (name.startsWith(ROLES_BASE))
273 return nodeRoles;
274 List<UserAdmin> res = new ArrayList<UserAdmin>(1);
275 for (LdapName baseDn : userAdmins.keySet()) {
276 if (name.startsWith(baseDn))
277 res.add(userAdmins.get(baseDn));
278 }
279 if (res.size() == 0)
280 throw new UserDirectoryException("Cannot find user admin for " + name);
281 if (res.size() > 1)
282 throw new UserDirectoryException("Multiple user admin found for " + name);
283 return res.get(0);
284 }
285
286 public void setTransactionManager(TransactionManager transactionManager) {
287 if (nodeRoles instanceof UserDirectory)
288 ((UserDirectory) nodeRoles).setTransactionManager(transactionManager);
289 for (UserAdmin userAdmin : userAdmins.values()) {
290 if (userAdmin instanceof UserDirectory)
291 ((UserDirectory) userAdmin).setTransactionManager(transactionManager);
292 }
293 }
294
295 private void initUserAdmins(String userAdminUri, File nodeBaseDir) {
296 if (userAdminUri == null) {
297 String demoBaseDn = "dc=example,dc=com";
298 File businessRolesFile = new File(nodeBaseDir, demoBaseDn + ".ldif");
299 if (!businessRolesFile.exists())
300 try {
301 FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn + ".ldif"),
302 businessRolesFile);
303 } catch (IOException e) {
304 throw new CmsException("Cannot copy demo resource", e);
305 }
306 userAdminUri = businessRolesFile.toURI().toString();
307 }
308 String[] uris = userAdminUri.split(" ");
309 for (String uri : uris) {
310 URI u;
311 try {
312 u = new URI(uri);
313 if (u.getPath() == null)
314 throw new CmsException("URI " + uri + " must have a path in order to determine base DN");
315 if (u.getScheme() == null) {
316 if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
317 u = new File(uri).getCanonicalFile().toURI();
318 else if (!uri.contains("/")) {
319 u = new URI(nodeBaseDir.toURI() + uri);
320 // u = new File(nodeBaseDir, uri).getCanonicalFile()
321 // .toURI();
322 } else
323 throw new CmsException("Cannot interpret " + uri + " as an uri");
324 } else if (u.getScheme().equals("file")) {
325 u = new File(u).getCanonicalFile().toURI();
326 }
327 } catch (Exception e) {
328 throw new CmsException("Cannot interpret " + uri + " as an uri", e);
329 }
330 Dictionary<String, ?> properties = UserAdminConf.uriAsProperties(u.toString());
331 UserDirectory businessRoles;
332 if (u.getScheme().startsWith("ldap")) {
333 businessRoles = new LdapUserAdmin(properties);
334 } else {
335 businessRoles = new LdifUserAdmin(properties);
336 }
337 businessRoles.init();
338 String baseDn = businessRoles.getBaseDn();
339 if (userAdmins.containsKey(baseDn))
340 throw new UserDirectoryException("There is already a user admin for " + baseDn);
341 try {
342 userAdmins.put(new LdapName(baseDn), (UserAdmin) businessRoles);
343 } catch (InvalidNameException e) {
344 throw new UserDirectoryException("Badly formatted base DN " + baseDn, e);
345 }
346 addUserAdmin(businessRoles.getBaseDn(), (UserAdmin) businessRoles);
347 if (log.isDebugEnabled())
348 log.debug("User directory " + businessRoles.getBaseDn() + " [" + u.getScheme() + "] enabled.");
349 }
350
351 }
352
353 private void initNodeRoles(String nodeRolesUri, File nodeBaseDir) {
354 String baseNodeRoleDn = AuthConstants.ROLES_BASEDN;
355 if (nodeRolesUri == null) {
356 File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif");
357 if (!nodeRolesFile.exists())
358 try {
359 FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn + ".ldif"),
360 nodeRolesFile);
361 } catch (IOException e) {
362 throw new CmsException("Cannot copy demo resource", e);
363 }
364 nodeRolesUri = nodeRolesFile.toURI().toString();
365 }
366
367 Dictionary<String, ?> nodeRolesProperties = UserAdminConf.uriAsProperties(nodeRolesUri);
368 if (!nodeRolesProperties.get(UserAdminConf.baseDn.name()).equals(baseNodeRoleDn)) {
369 throw new CmsException("Invalid base dn for node roles");
370 // TODO deal with "mounted" roles with a different baseDN
371 }
372 if (nodeRolesUri.startsWith("ldap")) {
373 nodeRoles = new LdapUserAdmin(nodeRolesProperties);
374 } else {
375 nodeRoles = new LdifUserAdmin(nodeRolesProperties);
376 }
377 ((UserDirectory) nodeRoles).setExternalRoles(this);
378 ((UserDirectory) nodeRoles).init();
379 addUserAdmin(baseNodeRoleDn, (UserAdmin) nodeRoles);
380 if (log.isTraceEnabled())
381 log.trace("Node roles enabled.");
382
383 }
384
385 /*
386 * JCR
387 */
388 // private void initJcr(Session adminSession) {
389 // try {
390 // JcrUtils.mkdirs(adminSession, homeBasePath);
391 // JcrUtils.mkdirs(adminSession, peopleBasePath);
392 // adminSession.save();
393 //
394 // JcrUtils.addPrivilege(adminSession, homeBasePath,
395 // AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
396 // JcrUtils.addPrivilege(adminSession, peopleBasePath,
397 // AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL);
398 // adminSession.save();
399 // } catch (RepositoryException e) {
400 // throw new CmsException("Cannot initialize node user admin", e);
401 // }
402 // }
403 //
404 // private Node syncJcr(Session session, Authorization authorization) {
405 // // TODO check user name validity (e.g. should not start by ROLE_)
406 // String username = authorization.getName();
407 // // String[] roles = authorization.getRoles();
408 // try {
409 // Node userHome = UserJcrUtils.getUserHome(session, username);
410 // if (userHome == null) {
411 // String homePath = generateUserPath(homeBasePath, username);
412 // if (session.itemExists(homePath))// duplicate user id
413 // userHome =
414 // session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
415 // else
416 // userHome = JcrUtils.mkdirs(session, homePath);
417 // // userHome = JcrUtils.mkfolders(session, homePath);
418 // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
419 // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
420 // session.save();
421 //
422 // JcrUtils.clearAccessControList(session, homePath, username);
423 // JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL);
424 // }
425 //
426 // Node userProfile = UserJcrUtils.getUserProfile(session, username);
427 // // new user
428 // if (userProfile == null) {
429 // String personPath = generateUserPath(peopleBasePath, username);
430 // Node personBase;
431 // if (session.itemExists(personPath))// duplicate user id
432 // personBase =
433 // session.getNode(personPath).getParent().addNode(JcrUtils.lastPathElement(personPath));
434 // else
435 // personBase = JcrUtils.mkdirs(session, personPath);
436 // userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE);
437 // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
438 // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
439 // userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
440 // userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true);
441 // userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true);
442 // userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true);
443 // session.save();
444 //
445 // JcrUtils.clearAccessControList(session, userProfile.getPath(), username);
446 // JcrUtils.addPrivilege(session, userProfile.getPath(), username,
447 // Privilege.JCR_READ);
448 // }
449 //
450 // // Remote roles
451 // // if (roles != null) {
452 // // writeRemoteRoles(userProfile, roles);
453 // // }
454 // if (adminSession.hasPendingChanges())
455 // adminSession.save();
456 // return userProfile;
457 // } catch (RepositoryException e) {
458 // JcrUtils.discardQuietly(session);
459 // throw new ArgeoException("Cannot sync node security model for " +
460 // username, e);
461 // }
462 // }
463 //
464 // /** Generate path for a new user home */
465 // private String generateUserPath(String base, String username) {
466 // LdapName dn;
467 // try {
468 // dn = new LdapName(username);
469 // } catch (InvalidNameException e) {
470 // throw new ArgeoException("Invalid name " + username, e);
471 // }
472 // String userId = dn.getRdn(dn.size() - 1).getValue().toString();
473 // int atIndex = userId.indexOf('@');
474 // if (atIndex > 0) {
475 // String domain = userId.substring(0, atIndex);
476 // String name = userId.substring(atIndex + 1);
477 // return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + domain +
478 // '/'
479 // + JcrUtils.firstCharsToPath(name, 2) + '/' + name;
480 // } else if (atIndex == 0 || atIndex == (userId.length() - 1)) {
481 // throw new ArgeoException("Unsupported username " + userId);
482 // } else {
483 // return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + userId;
484 // }
485 // }
486
487 // /** Write remote roles used by remote access in the home directory */
488 // private void writeRemoteRoles(Node userHome, String[] roles)
489 // throws RepositoryException {
490 // boolean writeRoles = false;
491 // if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
492 // Value[] remoteRoles = userHome.getProperty(
493 // ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
494 // if (remoteRoles.length != roles.length)
495 // writeRoles = true;
496 // else
497 // for (int i = 0; i < remoteRoles.length; i++)
498 // if (!remoteRoles[i].getString().equals(roles[i]))
499 // writeRoles = true;
500 // } else
501 // writeRoles = true;
502 //
503 // if (writeRoles) {
504 // userHome.getSession().getWorkspace().getVersionManager()
505 // .checkout(userHome.getPath());
506 // userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roles);
507 // JcrUtils.updateLastModified(userHome);
508 // userHome.getSession().save();
509 // userHome.getSession().getWorkspace().getVersionManager()
510 // .checkin(userHome.getPath());
511 // if (log.isDebugEnabled())
512 // log.debug("Wrote remote roles " + roles + " for "
513 // + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));
514 // }
515 //
516 // }
517 }