1 package org
.argeo
.cms
.internal
.kernel
;
4 import java
.net
.URISyntaxException
;
5 import java
.util
.ArrayList
;
6 import java
.util
.Arrays
;
7 import java
.util
.Dictionary
;
8 import java
.util
.HashMap
;
9 import java
.util
.HashSet
;
10 import java
.util
.Hashtable
;
11 import java
.util
.List
;
15 import javax
.naming
.InvalidNameException
;
16 import javax
.naming
.ldap
.LdapName
;
17 import javax
.transaction
.TransactionManager
;
19 import org
.apache
.commons
.logging
.Log
;
20 import org
.apache
.commons
.logging
.LogFactory
;
21 import org
.argeo
.cms
.CmsException
;
22 import org
.argeo
.cms
.auth
.AuthConstants
;
23 import org
.argeo
.node
.NodeConstants
;
24 import org
.argeo
.osgi
.useradmin
.LdapUserAdmin
;
25 import org
.argeo
.osgi
.useradmin
.LdifUserAdmin
;
26 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
27 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
28 import org
.argeo
.osgi
.useradmin
.UserDirectoryException
;
29 import org
.osgi
.framework
.BundleContext
;
30 import org
.osgi
.framework
.Constants
;
31 import org
.osgi
.framework
.FrameworkUtil
;
32 import org
.osgi
.framework
.InvalidSyntaxException
;
33 import org
.osgi
.framework
.ServiceReference
;
34 import org
.osgi
.framework
.ServiceRegistration
;
35 import org
.osgi
.service
.cm
.ConfigurationException
;
36 import org
.osgi
.service
.cm
.ManagedServiceFactory
;
37 import org
.osgi
.service
.useradmin
.Authorization
;
38 import org
.osgi
.service
.useradmin
.Role
;
39 import org
.osgi
.service
.useradmin
.User
;
40 import org
.osgi
.service
.useradmin
.UserAdmin
;
41 import org
.osgi
.util
.tracker
.ServiceTracker
;
43 import bitronix
.tm
.resource
.ehcache
.EhCacheXAResourceProducer
;
46 * Aggregates multiple {@link UserDirectory} and integrates them with this node
49 class NodeUserAdmin
implements UserAdmin
, ManagedServiceFactory
, KernelConstants
{
50 private final static Log log
= LogFactory
.getLog(NodeUserAdmin
.class);
51 final static LdapName ROLES_BASE
;
54 ROLES_BASE
= new LdapName(AuthConstants
.ROLES_BASEDN
);
55 } catch (InvalidNameException e
) {
56 throw new UserDirectoryException("Cannot initialize " + NodeUserAdmin
.class, e
);
60 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
63 private UserAdmin nodeRoles
= null;
64 private Map
<LdapName
, UserAdmin
> userAdmins
= new HashMap
<LdapName
, UserAdmin
>();
65 private Map
<String
, LdapName
> pidToBaseDn
= new HashMap
<>();
67 private ServiceRegistration
<UserAdmin
> userAdminReg
;
69 private final ServiceTracker
<TransactionManager
, TransactionManager
> tmTracker
;
72 // private String homeBasePath = "/home";
73 // private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH;
74 // private Repository repository;
75 // private Session adminSession;
77 private final String cacheName
= UserDirectory
.class.getName();
79 public NodeUserAdmin() {
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);
88 // new ServiceTracker<>(bc, TransactionManager.class, new
89 // TransactionManagerStc()).open();
90 tmTracker
= new TransactionManagerStc();
95 public void updated(String pid
, Dictionary
<String
, ?
> properties
) throws ConfigurationException
{
96 String uri
= (String
) properties
.get(UserAdminConf
.uri
.name());
100 } catch (URISyntaxException e
) {
101 throw new CmsException("Badly formatted URI " + uri
, e
);
103 UserDirectory userDirectory
= u
.getScheme().equals("ldap") ?
new LdapUserAdmin(properties
)
104 : new LdifUserAdmin(properties
);
107 baseDn
= new LdapName(userDirectory
.getBaseDn());
108 } catch (InvalidNameException e
) {
109 throw new CmsException("Badly formatted base DN " + userDirectory
.getBaseDn(), e
);
111 if (isRolesDnBase(baseDn
)) {
112 nodeRoles
= (UserAdmin
) userDirectory
;
113 userDirectory
.setExternalRoles(this);
115 userDirectory
.init();
116 addUserAdmin(baseDn
.toString(), (UserAdmin
) userDirectory
);
118 // publish user directory
119 Dictionary
<String
, Object
> regProps
= new Hashtable
<>();
120 regProps
.put(Constants
.SERVICE_PID
, pid
);
121 regProps
.put(UserAdminConf
.baseDn
.name(), baseDn
);
122 bc
.registerService(UserDirectory
.class, userDirectory
, regProps
);
123 pidToBaseDn
.put(pid
, baseDn
);
125 if (log
.isDebugEnabled()) {
126 log
.debug("User directory " + userDirectory
.getBaseDn() + " [" + u
.getScheme() + "] enabled.");
129 if (!isRolesDnBase(baseDn
)) {
130 if (userAdminReg
!= null)
131 userAdminReg
.unregister();
132 // register self as main user admin
133 userAdminReg
= bc
.registerService(UserAdmin
.class, this, currentState());
137 private boolean isRolesDnBase(LdapName baseDn
) {
138 return baseDn
.equals(ROLES_BASE
);
142 public void deleted(String pid
) {
143 LdapName baseDn
= pidToBaseDn
.remove(pid
);
144 UserAdmin userAdmin
= userAdmins
.remove(baseDn
);
145 ((UserDirectory
) userAdmin
).destroy();
149 public String
getName() {
150 return "Node user admin";
153 private class TransactionManagerStc
extends ServiceTracker
<TransactionManager
, TransactionManager
> {
155 public TransactionManagerStc() {
156 super(bc
, TransactionManager
.class, null);
160 public TransactionManager
addingService(ServiceReference
<TransactionManager
> reference
) {
161 TransactionManager transactionManager
= bc
.getService(reference
);
162 if (nodeRoles
!= null)
163 ((UserDirectory
) nodeRoles
).setTransactionManager(transactionManager
);
164 for (UserAdmin userAdmin
: userAdmins
.values()) {
165 if (userAdmin
instanceof UserDirectory
)
166 ((UserDirectory
) userAdmin
).setTransactionManager(transactionManager
);
168 if (log
.isDebugEnabled())
169 log
.debug("Set transaction manager");
170 return transactionManager
;
174 public void removedService(ServiceReference
<TransactionManager
> reference
, TransactionManager service
) {
175 ((UserDirectory
) nodeRoles
).setTransactionManager(null);
176 for (UserAdmin userAdmin
: userAdmins
.values()) {
177 if (userAdmin
instanceof UserDirectory
)
178 ((UserDirectory
) userAdmin
).setTransactionManager(null);
185 // public NodeUserAdmin(TransactionManager transactionManager, Repository
187 // // this.repository = repository;
189 // // this.adminSession = this.repository.login();
190 // // } catch (RepositoryException e) {
191 // // throw new CmsException("Cannot log-in", e);
195 // File nodeBaseDir = new File(getOsgiInstanceDir(), DIR_NODE);
196 // nodeBaseDir.mkdirs();
197 // String userAdminUri = getFrameworkProp(NodeConstants.USERADMIN_URIS);
198 // initUserAdmins(userAdminUri, nodeBaseDir);
199 // String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
200 // initNodeRoles(nodeRolesUri, nodeBaseDir);
202 // // Transaction manager
203 // ((UserDirectory) nodeRoles).setTransactionManager(transactionManager);
204 // for (UserAdmin userAdmin : userAdmins.values()) {
205 // if (userAdmin instanceof UserDirectory)
206 // ((UserDirectory) userAdmin).setTransactionManager(transactionManager);
210 // // initJcr(adminSession);
213 Dictionary
<String
, Object
> currentState() {
214 Dictionary
<String
, Object
> res
= new Hashtable
<String
, Object
>();
215 res
.put(NodeConstants
.CN
, NodeConstants
.DEFAULT
);
216 for (LdapName name
: userAdmins
.keySet()) {
217 StringBuilder buf
= new StringBuilder();
218 if (userAdmins
.get(name
) instanceof UserDirectory
) {
219 UserDirectory userDirectory
= (UserDirectory
) userAdmins
.get(name
);
220 String uri
= UserAdminConf
.propertiesAsUri(userDirectory
.getProperties()).toString();
223 buf
.append('/').append(name
.toString()).append("?readOnly=true");
229 public void destroy() {
230 for (LdapName name
: userAdmins
.keySet()) {
231 if (userAdmins
.get(name
) instanceof UserDirectory
) {
232 UserDirectory userDirectory
= (UserDirectory
) userAdmins
.get(name
);
234 // FIXME Make it less bitronix dependant
235 EhCacheXAResourceProducer
.unregisterXAResource(cacheName
, userDirectory
.getXaResource());
236 } catch (Exception e
) {
237 log
.error("Cannot unregister resource from Bitronix", e
);
239 userDirectory
.destroy();
246 public Role
createRole(String name
, int type
) {
247 return findUserAdmin(name
).createRole(name
, type
);
251 public boolean removeRole(String name
) {
252 boolean actuallyDeleted
= findUserAdmin(name
).removeRole(name
);
253 nodeRoles
.removeRole(name
);
254 return actuallyDeleted
;
258 public Role
getRole(String name
) {
259 return findUserAdmin(name
).getRole(name
);
263 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
264 List
<Role
> res
= new ArrayList
<Role
>();
265 for (UserAdmin userAdmin
: userAdmins
.values()) {
266 res
.addAll(Arrays
.asList(userAdmin
.getRoles(filter
)));
268 res
.addAll(Arrays
.asList(nodeRoles
.getRoles(filter
)));
269 return res
.toArray(new Role
[res
.size()]);
273 public User
getUser(String key
, String value
) {
274 List
<User
> res
= new ArrayList
<User
>();
275 for (UserAdmin userAdmin
: userAdmins
.values()) {
276 User u
= userAdmin
.getUser(key
, value
);
280 // Note: node roles cannot contain users, so it is not searched
281 return res
.size() == 1 ? res
.get(0) : null;
285 public Authorization
getAuthorization(User user
) {
286 if (user
== null) {// anonymous
287 return nodeRoles
.getAuthorization(null);
289 UserAdmin userAdmin
= findUserAdmin(user
.getName());
290 Authorization rawAuthorization
= userAdmin
.getAuthorization(user
);
291 // gather system roles
292 Set
<String
> systemRoles
= new HashSet
<String
>();
293 for (String role
: rawAuthorization
.getRoles()) {
294 Authorization auth
= nodeRoles
.getAuthorization((User
) userAdmin
.getRole(role
));
295 systemRoles
.addAll(Arrays
.asList(auth
.getRoles()));
297 Authorization authorization
= new NodeAuthorization(rawAuthorization
.getName(), rawAuthorization
.toString(),
298 systemRoles
, rawAuthorization
.getRoles());
299 // syncJcr(adminSession, authorization);
300 return authorization
;
304 // USER ADMIN AGGREGATOR
306 public void addUserAdmin(String baseDn
, UserAdmin userAdmin
) {
308 LdapName key
= new LdapName(baseDn
);
309 if (userAdmins
.containsKey(key
))
310 throw new UserDirectoryException("There is already a user admin for " + baseDn
);
311 userAdmins
.put(key
, userAdmin
);
312 } catch (InvalidNameException e
) {
313 throw new UserDirectoryException("Badly formatted base DN " + baseDn
, e
);
315 if (userAdmin
instanceof UserDirectory
) {
316 UserDirectory userDirectory
= (UserDirectory
) userAdmin
;
318 userDirectory
.setTransactionManager(tmTracker
.getService());
319 // FIXME Make it less bitronix dependant
320 EhCacheXAResourceProducer
.registerXAResource(cacheName
, ((UserDirectory
) userAdmin
).getXaResource());
321 } catch (Exception e
) {
322 log
.error("Cannot register resource to Bitronix", e
);
327 private UserAdmin
findUserAdmin(String name
) {
329 return findUserAdmin(new LdapName(name
));
330 } catch (InvalidNameException e
) {
331 throw new UserDirectoryException("Badly formatted name " + name
, e
);
335 private UserAdmin
findUserAdmin(LdapName name
) {
336 if (name
.startsWith(ROLES_BASE
))
338 List
<UserAdmin
> res
= new ArrayList
<UserAdmin
>(1);
339 for (LdapName baseDn
: userAdmins
.keySet()) {
340 if (name
.startsWith(baseDn
))
341 res
.add(userAdmins
.get(baseDn
));
344 throw new UserDirectoryException("Cannot find user admin for " + name
);
346 throw new UserDirectoryException("Multiple user admin found for " + name
);
350 public void setTransactionManager(TransactionManager transactionManager
) {
351 if (nodeRoles
instanceof UserDirectory
)
352 ((UserDirectory
) nodeRoles
).setTransactionManager(transactionManager
);
353 for (UserAdmin userAdmin
: userAdmins
.values()) {
354 if (userAdmin
instanceof UserDirectory
)
355 ((UserDirectory
) userAdmin
).setTransactionManager(transactionManager
);
359 // private void initUserAdmins(String userAdminUri, File nodeBaseDir) {
360 // // if (userAdminUri == null) {
361 // // String demoBaseDn = "dc=example,dc=com";
362 // // File businessRolesFile = new File(nodeBaseDir, demoBaseDn + ".ldif");
363 // // if (!businessRolesFile.exists())
366 // FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn
368 // // businessRolesFile);
369 // // } catch (IOException e) {
370 // // throw new CmsException("Cannot copy demo resource", e);
372 // // userAdminUri = businessRolesFile.toURI().toString();
374 // String[] uris = userAdminUri.split(" ");
375 // for (String uri : uris) {
379 // if (u.getPath() == null)
380 // throw new CmsException("URI " + uri + " must have a path in order to
381 // determine base DN");
382 // if (u.getScheme() == null) {
383 // if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
384 // u = new File(uri).getCanonicalFile().toURI();
385 // else if (!uri.contains("/")) {
386 // u = new URI(nodeBaseDir.toURI() + uri);
387 // // u = new File(nodeBaseDir, uri).getCanonicalFile()
390 // throw new CmsException("Cannot interpret " + uri + " as an uri");
391 // } else if (u.getScheme().equals("file")) {
392 // u = new File(u).getCanonicalFile().toURI();
394 // } catch (Exception e) {
395 // throw new CmsException("Cannot interpret " + uri + " as an uri", e);
397 // Dictionary<String, ?> properties =
398 // UserAdminConf.uriAsProperties(u.toString());
399 // UserDirectory businessRoles;
400 // if (u.getScheme().startsWith("ldap")) {
401 // businessRoles = new LdapUserAdmin(properties);
403 // businessRoles = new LdifUserAdmin(properties);
405 // businessRoles.init();
406 // String baseDn = businessRoles.getBaseDn();
407 // if (userAdmins.containsKey(baseDn))
408 // throw new UserDirectoryException("There is already a user admin for " +
411 // userAdmins.put(new LdapName(baseDn), (UserAdmin) businessRoles);
412 // } catch (InvalidNameException e) {
413 // throw new UserDirectoryException("Badly formatted base DN " + baseDn, e);
415 // addUserAdmin(businessRoles.getBaseDn(), (UserAdmin) businessRoles);
416 // if (log.isDebugEnabled())
417 // log.debug("User directory " + businessRoles.getBaseDn() + " [" +
418 // u.getScheme() + "] enabled.");
423 // private void initNodeRoles(String nodeRolesUri, File nodeBaseDir) {
424 // String baseNodeRoleDn = AuthConstants.ROLES_BASEDN;
425 // if (nodeRolesUri == null) {
426 // File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif");
427 // if (!nodeRolesFile.exists())
429 // FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn
432 // } catch (IOException e) {
433 // throw new CmsException("Cannot copy demo resource", e);
435 // nodeRolesUri = nodeRolesFile.toURI().toString();
438 // Dictionary<String, ?> nodeRolesProperties =
439 // UserAdminConf.uriAsProperties(nodeRolesUri);
441 // (!nodeRolesProperties.get(UserAdminConf.baseDn.name()).equals(baseNodeRoleDn))
443 // throw new CmsException("Invalid base dn for node roles");
444 // // TODO deal with "mounted" roles with a different baseDN
446 // if (nodeRolesUri.startsWith("ldap")) {
447 // nodeRoles = new LdapUserAdmin(nodeRolesProperties);
449 // nodeRoles = new LdifUserAdmin(nodeRolesProperties);
451 // ((UserDirectory) nodeRoles).setExternalRoles(this);
452 // ((UserDirectory) nodeRoles).init();
453 // addUserAdmin(baseNodeRoleDn, (UserAdmin) nodeRoles);
454 // if (log.isTraceEnabled())
455 // log.trace("Node roles enabled.");
462 // private void initJcr(Session adminSession) {
464 // JcrUtils.mkdirs(adminSession, homeBasePath);
465 // JcrUtils.mkdirs(adminSession, peopleBasePath);
466 // adminSession.save();
468 // JcrUtils.addPrivilege(adminSession, homeBasePath,
469 // AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
470 // JcrUtils.addPrivilege(adminSession, peopleBasePath,
471 // AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL);
472 // adminSession.save();
473 // } catch (RepositoryException e) {
474 // throw new CmsException("Cannot initialize node user admin", e);
478 // private Node syncJcr(Session session, Authorization authorization) {
479 // // TODO check user name validity (e.g. should not start by ROLE_)
480 // String username = authorization.getName();
481 // // String[] roles = authorization.getRoles();
483 // Node userHome = UserJcrUtils.getUserHome(session, username);
484 // if (userHome == null) {
485 // String homePath = generateUserPath(homeBasePath, username);
486 // if (session.itemExists(homePath))// duplicate user id
488 // session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
490 // userHome = JcrUtils.mkdirs(session, homePath);
491 // // userHome = JcrUtils.mkfolders(session, homePath);
492 // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
493 // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
496 // JcrUtils.clearAccessControList(session, homePath, username);
497 // JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL);
500 // Node userProfile = UserJcrUtils.getUserProfile(session, username);
502 // if (userProfile == null) {
503 // String personPath = generateUserPath(peopleBasePath, username);
505 // if (session.itemExists(personPath))// duplicate user id
507 // session.getNode(personPath).getParent().addNode(JcrUtils.lastPathElement(personPath));
509 // personBase = JcrUtils.mkdirs(session, personPath);
510 // userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE);
511 // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
512 // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
513 // userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
514 // userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true);
515 // userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true);
516 // userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true);
519 // JcrUtils.clearAccessControList(session, userProfile.getPath(), username);
520 // JcrUtils.addPrivilege(session, userProfile.getPath(), username,
521 // Privilege.JCR_READ);
525 // // if (roles != null) {
526 // // writeRemoteRoles(userProfile, roles);
528 // if (adminSession.hasPendingChanges())
529 // adminSession.save();
530 // return userProfile;
531 // } catch (RepositoryException e) {
532 // JcrUtils.discardQuietly(session);
533 // throw new ArgeoException("Cannot sync node security model for " +
538 // /** Generate path for a new user home */
539 // private String generateUserPath(String base, String username) {
542 // dn = new LdapName(username);
543 // } catch (InvalidNameException e) {
544 // throw new ArgeoException("Invalid name " + username, e);
546 // String userId = dn.getRdn(dn.size() - 1).getValue().toString();
547 // int atIndex = userId.indexOf('@');
548 // if (atIndex > 0) {
549 // String domain = userId.substring(0, atIndex);
550 // String name = userId.substring(atIndex + 1);
551 // return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + domain +
553 // + JcrUtils.firstCharsToPath(name, 2) + '/' + name;
554 // } else if (atIndex == 0 || atIndex == (userId.length() - 1)) {
555 // throw new ArgeoException("Unsupported username " + userId);
557 // return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + userId;
561 // /** Write remote roles used by remote access in the home directory */
562 // private void writeRemoteRoles(Node userHome, String[] roles)
563 // throws RepositoryException {
564 // boolean writeRoles = false;
565 // if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
566 // Value[] remoteRoles = userHome.getProperty(
567 // ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
568 // if (remoteRoles.length != roles.length)
569 // writeRoles = true;
571 // for (int i = 0; i < remoteRoles.length; i++)
572 // if (!remoteRoles[i].getString().equals(roles[i]))
573 // writeRoles = true;
575 // writeRoles = true;
578 // userHome.getSession().getWorkspace().getVersionManager()
579 // .checkout(userHome.getPath());
580 // userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roles);
581 // JcrUtils.updateLastModified(userHome);
582 // userHome.getSession().save();
583 // userHome.getSession().getWorkspace().getVersionManager()
584 // .checkin(userHome.getPath());
585 // if (log.isDebugEnabled())
586 // log.debug("Wrote remote roles " + roles + " for "
587 // + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));