1 package org
.argeo
.cms
.internal
.kernel
;
3 import static org
.argeo
.cms
.internal
.kernel
.KernelUtils
.getFrameworkProp
;
4 import static org
.argeo
.cms
.internal
.kernel
.KernelUtils
.getOsgiInstanceDir
;
7 import java
.io
.IOException
;
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
;
19 import javax
.jcr
.Repository
;
20 import javax
.naming
.InvalidNameException
;
21 import javax
.naming
.ldap
.LdapName
;
22 import javax
.transaction
.TransactionManager
;
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
;
48 import bitronix
.tm
.resource
.ehcache
.EhCacheXAResourceProducer
;
51 * Aggregates multiple {@link UserDirectory} and integrates them with this node
54 class NodeUserAdmin
implements UserAdmin
, ManagedService
, KernelConstants
{
55 private final static Log log
= LogFactory
.getLog(NodeUserAdmin
.class);
56 final static LdapName ROLES_BASE
;
59 ROLES_BASE
= new LdapName(AuthConstants
.ROLES_BASEDN
);
60 } catch (InvalidNameException e
) {
61 throw new UserDirectoryException("Cannot initialize " + NodeUserAdmin
.class, e
);
65 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
68 private UserAdmin nodeRoles
= null;
69 private Map
<LdapName
, UserAdmin
> userAdmins
= new HashMap
<LdapName
, UserAdmin
>();
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
);
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 TransactionManagerStc()).open();
92 public void updated(Dictionary
<String
, ?
> properties
) throws ConfigurationException
{
95 private class TransactionManagerStc
implements ServiceTrackerCustomizer
<TransactionManager
, TransactionManager
> {
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
);
105 if (log
.isDebugEnabled())
106 log
.debug("Set transaction manager");
107 return transactionManager
;
111 public void modifiedService(ServiceReference
<TransactionManager
> reference
, TransactionManager service
) {
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);
126 public NodeUserAdmin(TransactionManager transactionManager
, Repository repository
) {
127 // this.repository = repository;
129 // this.adminSession = this.repository.login();
130 // } catch (RepositoryException e) {
131 // throw new CmsException("Cannot log-in", e);
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
);
142 // Transaction manager
143 ((UserDirectory
) nodeRoles
).setTransactionManager(transactionManager
);
144 for (UserAdmin userAdmin
: userAdmins
.values()) {
145 if (userAdmin
instanceof UserDirectory
)
146 ((UserDirectory
) userAdmin
).setTransactionManager(transactionManager
);
150 // initJcr(adminSession);
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();
162 buf
.append('/').append(name
.toString()).append("?readOnly=true");
168 public void destroy() {
169 for (LdapName name
: userAdmins
.keySet()) {
170 if (userAdmins
.get(name
) instanceof UserDirectory
) {
171 UserDirectory userDirectory
= (UserDirectory
) userAdmins
.get(name
);
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
);
178 userDirectory
.destroy();
185 public Role
createRole(String name
, int type
) {
186 return findUserAdmin(name
).createRole(name
, type
);
190 public boolean removeRole(String name
) {
191 boolean actuallyDeleted
= findUserAdmin(name
).removeRole(name
);
192 nodeRoles
.removeRole(name
);
193 return actuallyDeleted
;
197 public Role
getRole(String name
) {
198 return findUserAdmin(name
).getRole(name
);
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
)));
207 res
.addAll(Arrays
.asList(nodeRoles
.getRoles(filter
)));
208 return res
.toArray(new Role
[res
.size()]);
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
);
219 // Note: node roles cannot contain users, so it is not searched
220 return res
.size() == 1 ? res
.get(0) : null;
224 public Authorization
getAuthorization(User user
) {
225 if (user
== null) {// anonymous
226 return nodeRoles
.getAuthorization(null);
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()));
236 Authorization authorization
= new NodeAuthorization(rawAuthorization
.getName(), rawAuthorization
.toString(),
237 systemRoles
, rawAuthorization
.getRoles());
238 // syncJcr(adminSession, authorization);
239 return authorization
;
243 // USER ADMIN AGGREGATOR
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
);
249 userAdmins
.put(new LdapName(baseDn
), userAdmin
);
250 } catch (InvalidNameException e
) {
251 throw new UserDirectoryException("Badly formatted base DN " + baseDn
, e
);
253 if (userAdmin
instanceof UserDirectory
) {
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
);
263 private UserAdmin
findUserAdmin(String name
) {
265 return findUserAdmin(new LdapName(name
));
266 } catch (InvalidNameException e
) {
267 throw new UserDirectoryException("Badly formatted name " + name
, e
);
271 private UserAdmin
findUserAdmin(LdapName name
) {
272 if (name
.startsWith(ROLES_BASE
))
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
));
280 throw new UserDirectoryException("Cannot find user admin for " + name
);
282 throw new UserDirectoryException("Multiple user admin found for " + name
);
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
);
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())
301 FileUtils
.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn
+ ".ldif"),
303 } catch (IOException e
) {
304 throw new CmsException("Cannot copy demo resource", e
);
306 userAdminUri
= businessRolesFile
.toURI().toString();
308 String
[] uris
= userAdminUri
.split(" ");
309 for (String uri
: uris
) {
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()
323 throw new CmsException("Cannot interpret " + uri
+ " as an uri");
324 } else if (u
.getScheme().equals("file")) {
325 u
= new File(u
).getCanonicalFile().toURI();
327 } catch (Exception e
) {
328 throw new CmsException("Cannot interpret " + uri
+ " as an uri", e
);
330 Dictionary
<String
, ?
> properties
= UserAdminConf
.uriAsProperties(u
.toString());
331 UserDirectory businessRoles
;
332 if (u
.getScheme().startsWith("ldap")) {
333 businessRoles
= new LdapUserAdmin(properties
);
335 businessRoles
= new LdifUserAdmin(properties
);
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
);
342 userAdmins
.put(new LdapName(baseDn
), (UserAdmin
) businessRoles
);
343 } catch (InvalidNameException e
) {
344 throw new UserDirectoryException("Badly formatted base DN " + baseDn
, e
);
346 addUserAdmin(businessRoles
.getBaseDn(), (UserAdmin
) businessRoles
);
347 if (log
.isDebugEnabled())
348 log
.debug("User directory " + businessRoles
.getBaseDn() + " [" + u
.getScheme() + "] enabled.");
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())
359 FileUtils
.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn
+ ".ldif"),
361 } catch (IOException e
) {
362 throw new CmsException("Cannot copy demo resource", e
);
364 nodeRolesUri
= nodeRolesFile
.toURI().toString();
367 Dictionary
<String
, ?
> nodeRolesProperties
= UserAdminConf
.uriAsProperties(nodeRolesUri
);
368 if (!nodeRolesProperties
.get(UserAdminConf
.baseDn
.property()).equals(baseNodeRoleDn
)) {
369 throw new CmsException("Invalid base dn for node roles");
370 // TODO deal with "mounted" roles with a different baseDN
372 if (nodeRolesUri
.startsWith("ldap")) {
373 nodeRoles
= new LdapUserAdmin(nodeRolesProperties
);
375 nodeRoles
= new LdifUserAdmin(nodeRolesProperties
);
377 ((UserDirectory
) nodeRoles
).setExternalRoles(this);
378 ((UserDirectory
) nodeRoles
).init();
379 addUserAdmin(baseNodeRoleDn
, (UserAdmin
) nodeRoles
);
380 if (log
.isTraceEnabled())
381 log
.trace("Node roles enabled.");
388 // private void initJcr(Session adminSession) {
390 // JcrUtils.mkdirs(adminSession, homeBasePath);
391 // JcrUtils.mkdirs(adminSession, peopleBasePath);
392 // adminSession.save();
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);
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();
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
414 // session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
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);
422 // JcrUtils.clearAccessControList(session, homePath, username);
423 // JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL);
426 // Node userProfile = UserJcrUtils.getUserProfile(session, username);
428 // if (userProfile == null) {
429 // String personPath = generateUserPath(peopleBasePath, username);
431 // if (session.itemExists(personPath))// duplicate user id
433 // session.getNode(personPath).getParent().addNode(JcrUtils.lastPathElement(personPath));
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);
445 // JcrUtils.clearAccessControList(session, userProfile.getPath(), username);
446 // JcrUtils.addPrivilege(session, userProfile.getPath(), username,
447 // Privilege.JCR_READ);
451 // // if (roles != null) {
452 // // writeRemoteRoles(userProfile, roles);
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 " +
464 // /** Generate path for a new user home */
465 // private String generateUserPath(String base, String username) {
468 // dn = new LdapName(username);
469 // } catch (InvalidNameException e) {
470 // throw new ArgeoException("Invalid name " + username, e);
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 +
479 // + JcrUtils.firstCharsToPath(name, 2) + '/' + name;
480 // } else if (atIndex == 0 || atIndex == (userId.length() - 1)) {
481 // throw new ArgeoException("Unsupported username " + userId);
483 // return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + userId;
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;
497 // for (int i = 0; i < remoteRoles.length; i++)
498 // if (!remoteRoles[i].getString().equals(roles[i]))
499 // writeRoles = true;
501 // writeRoles = true;
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));