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
.Node
;
20 import javax
.jcr
.Repository
;
21 import javax
.jcr
.RepositoryException
;
22 import javax
.jcr
.Session
;
23 import javax
.jcr
.security
.Privilege
;
24 import javax
.naming
.InvalidNameException
;
25 import javax
.naming
.ldap
.LdapName
;
26 import javax
.transaction
.TransactionManager
;
28 import org
.apache
.commons
.io
.FileUtils
;
29 import org
.apache
.commons
.logging
.Log
;
30 import org
.apache
.commons
.logging
.LogFactory
;
31 import org
.argeo
.ArgeoException
;
32 import org
.argeo
.cms
.CmsException
;
33 import org
.argeo
.cms
.auth
.AuthConstants
;
34 import org
.argeo
.jcr
.ArgeoJcrConstants
;
35 import org
.argeo
.jcr
.ArgeoNames
;
36 import org
.argeo
.jcr
.ArgeoTypes
;
37 import org
.argeo
.jcr
.JcrUtils
;
38 import org
.argeo
.jcr
.UserJcrUtils
;
39 import org
.argeo
.osgi
.useradmin
.LdapUserAdmin
;
40 import org
.argeo
.osgi
.useradmin
.LdifUserAdmin
;
41 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
42 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
43 import org
.argeo
.osgi
.useradmin
.UserDirectoryException
;
44 import org
.osgi
.framework
.InvalidSyntaxException
;
45 import org
.osgi
.service
.useradmin
.Authorization
;
46 import org
.osgi
.service
.useradmin
.Role
;
47 import org
.osgi
.service
.useradmin
.User
;
48 import org
.osgi
.service
.useradmin
.UserAdmin
;
50 import bitronix
.tm
.resource
.ehcache
.EhCacheXAResourceProducer
;
53 * Aggregates multiple {@link UserDirectory} and integrates them with this node
56 public class NodeUserAdmin
implements UserAdmin
, KernelConstants
{
57 private final static Log log
= LogFactory
.getLog(NodeUserAdmin
.class);
58 final static LdapName ROLES_BASE
;
61 ROLES_BASE
= new LdapName(AuthConstants
.ROLES_BASEDN
);
62 } catch (InvalidNameException e
) {
63 throw new UserDirectoryException("Cannot initialize " + NodeUserAdmin
.class, e
);
68 private UserAdmin nodeRoles
= null;
69 private Map
<LdapName
, UserAdmin
> userAdmins
= new HashMap
<LdapName
, UserAdmin
>();
72 /** The home base path. */
73 private String homeBasePath
= "/home";
74 private String peopleBasePath
= ArgeoJcrConstants
.PEOPLE_BASE_PATH
;
75 private Repository repository
;
76 private Session adminSession
;
78 private final String cacheName
= UserDirectory
.class.getName();
80 public NodeUserAdmin(TransactionManager transactionManager
, Repository repository
) {
81 this.repository
= repository
;
83 this.adminSession
= this.repository
.login();
84 } catch (RepositoryException e
) {
85 throw new CmsException("Cannot log-in", e
);
89 File nodeBaseDir
= new File(getOsgiInstanceDir(), DIR_NODE
);
91 String userAdminUri
= getFrameworkProp(USERADMIN_URIS
);
92 initUserAdmins(userAdminUri
, nodeBaseDir
);
93 String nodeRolesUri
= getFrameworkProp(ROLES_URI
);
94 initNodeRoles(nodeRolesUri
, nodeBaseDir
);
96 // Transaction manager
97 ((UserDirectory
) nodeRoles
).setTransactionManager(transactionManager
);
98 for (UserAdmin userAdmin
: userAdmins
.values()) {
99 if (userAdmin
instanceof UserDirectory
)
100 ((UserDirectory
) userAdmin
).setTransactionManager(transactionManager
);
104 initJcr(adminSession
);
107 Dictionary
<String
, ?
> currentState() {
108 Dictionary
<String
, Object
> res
= new Hashtable
<String
, Object
>();
109 for (LdapName name
: userAdmins
.keySet()) {
110 StringBuilder buf
= new StringBuilder();
111 if (userAdmins
.get(name
) instanceof UserDirectory
) {
112 UserDirectory userDirectory
= (UserDirectory
) userAdmins
.get(name
);
113 String uri
= UserAdminConf
.propertiesAsUri(userDirectory
.getProperties()).toString();
116 buf
.append('/').append(name
.toString()).append("?readOnly=true");
122 public void destroy() {
123 for (LdapName name
: userAdmins
.keySet()) {
124 if (userAdmins
.get(name
) instanceof UserDirectory
) {
125 UserDirectory userDirectory
= (UserDirectory
) userAdmins
.get(name
);
127 // FIXME Make it less bitronix dependant
128 EhCacheXAResourceProducer
.unregisterXAResource(cacheName
, userDirectory
.getXaResource());
129 } catch (Exception e
) {
130 log
.error("Cannot unregister resource from Bitronix", e
);
132 userDirectory
.destroy();
139 public Role
createRole(String name
, int type
) {
140 return findUserAdmin(name
).createRole(name
, type
);
144 public boolean removeRole(String name
) {
145 boolean actuallyDeleted
= findUserAdmin(name
).removeRole(name
);
146 nodeRoles
.removeRole(name
);
147 return actuallyDeleted
;
151 public Role
getRole(String name
) {
152 return findUserAdmin(name
).getRole(name
);
156 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
157 List
<Role
> res
= new ArrayList
<Role
>();
158 for (UserAdmin userAdmin
: userAdmins
.values()) {
159 res
.addAll(Arrays
.asList(userAdmin
.getRoles(filter
)));
161 res
.addAll(Arrays
.asList(nodeRoles
.getRoles(filter
)));
162 return res
.toArray(new Role
[res
.size()]);
166 public User
getUser(String key
, String value
) {
167 List
<User
> res
= new ArrayList
<User
>();
168 for (UserAdmin userAdmin
: userAdmins
.values()) {
169 User u
= userAdmin
.getUser(key
, value
);
173 // Note: node roles cannot contain users, so it is not searched
174 return res
.size() == 1 ? res
.get(0) : null;
178 public Authorization
getAuthorization(User user
) {
179 if (user
== null) {// anonymous
180 return nodeRoles
.getAuthorization(null);
182 UserAdmin userAdmin
= findUserAdmin(user
.getName());
183 Authorization rawAuthorization
= userAdmin
.getAuthorization(user
);
184 // gather system roles
185 Set
<String
> systemRoles
= new HashSet
<String
>();
186 for (String role
: rawAuthorization
.getRoles()) {
187 Authorization auth
= nodeRoles
.getAuthorization((User
) userAdmin
.getRole(role
));
188 systemRoles
.addAll(Arrays
.asList(auth
.getRoles()));
190 Authorization authorization
= new NodeAuthorization(rawAuthorization
.getName(), rawAuthorization
.toString(),
191 systemRoles
, rawAuthorization
.getRoles());
192 syncJcr(adminSession
, authorization
);
193 return authorization
;
197 // USER ADMIN AGGREGATOR
199 public void addUserAdmin(String baseDn
, UserAdmin userAdmin
) {
200 if (userAdmins
.containsKey(baseDn
))
201 throw new UserDirectoryException("There is already a user admin for " + baseDn
);
203 userAdmins
.put(new LdapName(baseDn
), userAdmin
);
204 } catch (InvalidNameException e
) {
205 throw new UserDirectoryException("Badly formatted base DN " + baseDn
, e
);
207 if (userAdmin
instanceof UserDirectory
) {
209 // FIXME Make it less bitronix dependant
210 EhCacheXAResourceProducer
.registerXAResource(cacheName
, ((UserDirectory
) userAdmin
).getXaResource());
211 } catch (Exception e
) {
212 log
.error("Cannot register resource to Bitronix", e
);
217 private UserAdmin
findUserAdmin(String name
) {
219 return findUserAdmin(new LdapName(name
));
220 } catch (InvalidNameException e
) {
221 throw new UserDirectoryException("Badly formatted name " + name
, e
);
225 private UserAdmin
findUserAdmin(LdapName name
) {
226 if (name
.startsWith(ROLES_BASE
))
228 List
<UserAdmin
> res
= new ArrayList
<UserAdmin
>(1);
229 for (LdapName baseDn
: userAdmins
.keySet()) {
230 if (name
.startsWith(baseDn
))
231 res
.add(userAdmins
.get(baseDn
));
234 throw new UserDirectoryException("Cannot find user admin for " + name
);
236 throw new UserDirectoryException("Multiple user admin found for " + name
);
240 public void setTransactionManager(TransactionManager transactionManager
) {
241 if (nodeRoles
instanceof UserDirectory
)
242 ((UserDirectory
) nodeRoles
).setTransactionManager(transactionManager
);
243 for (UserAdmin userAdmin
: userAdmins
.values()) {
244 if (userAdmin
instanceof UserDirectory
)
245 ((UserDirectory
) userAdmin
).setTransactionManager(transactionManager
);
249 private void initUserAdmins(String userAdminUri
, File nodeBaseDir
) {
250 if (userAdminUri
== null) {
251 String demoBaseDn
= "dc=example,dc=com";
252 File businessRolesFile
= new File(nodeBaseDir
, demoBaseDn
+ ".ldif");
253 if (!businessRolesFile
.exists())
255 FileUtils
.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn
+ ".ldif"),
257 } catch (IOException e
) {
258 throw new CmsException("Cannot copy demo resource", e
);
260 userAdminUri
= businessRolesFile
.toURI().toString();
262 String
[] uris
= userAdminUri
.split(" ");
263 for (String uri
: uris
) {
267 if (u
.getPath() == null)
268 throw new CmsException("URI " + uri
+ " must have a path in order to determine base DN");
269 if (u
.getScheme() == null) {
270 if (uri
.startsWith("/") || uri
.startsWith("./") || uri
.startsWith("../"))
271 u
= new File(uri
).getCanonicalFile().toURI();
272 else if (!uri
.contains("/")) {
273 u
= new URI(nodeBaseDir
.toURI() + uri
);
274 // u = new File(nodeBaseDir, uri).getCanonicalFile()
277 throw new CmsException("Cannot interpret " + uri
+ " as an uri");
278 } else if (u
.getScheme().equals("file")) {
279 u
= new File(u
).getCanonicalFile().toURI();
281 } catch (Exception e
) {
282 throw new CmsException("Cannot interpret " + uri
+ " as an uri", e
);
284 Dictionary
<String
, ?
> properties
= UserAdminConf
.uriAsProperties(u
.toString());
285 UserDirectory businessRoles
;
286 if (u
.getScheme().startsWith("ldap")) {
287 businessRoles
= new LdapUserAdmin(properties
);
289 businessRoles
= new LdifUserAdmin(properties
);
291 businessRoles
.init();
292 String baseDn
= businessRoles
.getBaseDn();
293 if (userAdmins
.containsKey(baseDn
))
294 throw new UserDirectoryException("There is already a user admin for " + baseDn
);
296 userAdmins
.put(new LdapName(baseDn
), (UserAdmin
) businessRoles
);
297 } catch (InvalidNameException e
) {
298 throw new UserDirectoryException("Badly formatted base DN " + baseDn
, e
);
300 addUserAdmin(businessRoles
.getBaseDn(), (UserAdmin
) businessRoles
);
301 if (log
.isDebugEnabled())
302 log
.debug("User directory " + businessRoles
.getBaseDn() + " [" + u
.getScheme() + "] enabled.");
307 private void initNodeRoles(String nodeRolesUri
, File nodeBaseDir
) {
308 String baseNodeRoleDn
= AuthConstants
.ROLES_BASEDN
;
309 if (nodeRolesUri
== null) {
310 File nodeRolesFile
= new File(nodeBaseDir
, baseNodeRoleDn
+ ".ldif");
311 if (!nodeRolesFile
.exists())
313 FileUtils
.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn
+ ".ldif"),
315 } catch (IOException e
) {
316 throw new CmsException("Cannot copy demo resource", e
);
318 nodeRolesUri
= nodeRolesFile
.toURI().toString();
321 Dictionary
<String
, ?
> nodeRolesProperties
= UserAdminConf
.uriAsProperties(nodeRolesUri
);
322 if (!nodeRolesProperties
.get(UserAdminConf
.baseDn
.property()).equals(baseNodeRoleDn
)) {
323 throw new CmsException("Invalid base dn for node roles");
324 // TODO deal with "mounted" roles with a different baseDN
326 if (nodeRolesUri
.startsWith("ldap")) {
327 nodeRoles
= new LdapUserAdmin(nodeRolesProperties
);
329 nodeRoles
= new LdifUserAdmin(nodeRolesProperties
);
331 ((UserDirectory
) nodeRoles
).setExternalRoles(this);
332 ((UserDirectory
) nodeRoles
).init();
333 addUserAdmin(baseNodeRoleDn
, (UserAdmin
) nodeRoles
);
334 if (log
.isTraceEnabled())
335 log
.trace("Node roles enabled.");
342 private void initJcr(Session adminSession
) {
344 JcrUtils
.mkdirs(adminSession
, homeBasePath
);
345 JcrUtils
.mkdirs(adminSession
, peopleBasePath
);
348 JcrUtils
.addPrivilege(adminSession
, homeBasePath
, AuthConstants
.ROLE_USER_ADMIN
, Privilege
.JCR_READ
);
349 JcrUtils
.addPrivilege(adminSession
, peopleBasePath
, AuthConstants
.ROLE_USER_ADMIN
, Privilege
.JCR_ALL
);
351 } catch (RepositoryException e
) {
352 throw new CmsException("Cannot initialize node user admin", e
);
356 private Node
syncJcr(Session session
, Authorization authorization
) {
357 // TODO check user name validity (e.g. should not start by ROLE_)
358 String username
= authorization
.getName();
359 // String[] roles = authorization.getRoles();
361 Node userHome
= UserJcrUtils
.getUserHome(session
, username
);
362 if (userHome
== null) {
363 String homePath
= generateUserPath(homeBasePath
, username
);
364 if (session
.itemExists(homePath
))// duplicate user id
365 userHome
= session
.getNode(homePath
).getParent().addNode(JcrUtils
.lastPathElement(homePath
));
367 userHome
= JcrUtils
.mkdirs(session
, homePath
);
368 // userHome = JcrUtils.mkfolders(session, homePath);
369 userHome
.addMixin(ArgeoTypes
.ARGEO_USER_HOME
);
370 userHome
.setProperty(ArgeoNames
.ARGEO_USER_ID
, username
);
373 JcrUtils
.clearAccessControList(session
, homePath
, username
);
374 JcrUtils
.addPrivilege(session
, homePath
, username
, Privilege
.JCR_ALL
);
377 Node userProfile
= UserJcrUtils
.getUserProfile(session
, username
);
379 if (userProfile
== null) {
380 String personPath
= generateUserPath(peopleBasePath
, username
);
382 if (session
.itemExists(personPath
))// duplicate user id
383 personBase
= session
.getNode(personPath
).getParent().addNode(JcrUtils
.lastPathElement(personPath
));
385 personBase
= JcrUtils
.mkdirs(session
, personPath
);
386 userProfile
= personBase
.addNode(ArgeoNames
.ARGEO_PROFILE
);
387 userProfile
.addMixin(ArgeoTypes
.ARGEO_USER_PROFILE
);
388 userProfile
.setProperty(ArgeoNames
.ARGEO_USER_ID
, username
);
389 userProfile
.setProperty(ArgeoNames
.ARGEO_ENABLED
, true);
390 userProfile
.setProperty(ArgeoNames
.ARGEO_ACCOUNT_NON_EXPIRED
, true);
391 userProfile
.setProperty(ArgeoNames
.ARGEO_ACCOUNT_NON_LOCKED
, true);
392 userProfile
.setProperty(ArgeoNames
.ARGEO_CREDENTIALS_NON_EXPIRED
, true);
395 JcrUtils
.clearAccessControList(session
, userProfile
.getPath(), username
);
396 JcrUtils
.addPrivilege(session
, userProfile
.getPath(), username
, Privilege
.JCR_READ
);
400 // if (roles != null) {
401 // writeRemoteRoles(userProfile, roles);
403 if (adminSession
.hasPendingChanges())
406 } catch (RepositoryException e
) {
407 JcrUtils
.discardQuietly(session
);
408 throw new ArgeoException("Cannot sync node security model for " + username
, e
);
412 /** Generate path for a new user home */
413 private String
generateUserPath(String base
, String username
) {
416 dn
= new LdapName(username
);
417 } catch (InvalidNameException e
) {
418 throw new ArgeoException("Invalid name " + username
, e
);
420 String userId
= dn
.getRdn(dn
.size() - 1).getValue().toString();
421 int atIndex
= userId
.indexOf('@');
423 String domain
= userId
.substring(0, atIndex
);
424 String name
= userId
.substring(atIndex
+ 1);
425 return base
+ '/' + JcrUtils
.firstCharsToPath(domain
, 2) + '/' + domain
+ '/'
426 + JcrUtils
.firstCharsToPath(name
, 2) + '/' + name
;
427 } else if (atIndex
== 0 || atIndex
== (userId
.length() - 1)) {
428 throw new ArgeoException("Unsupported username " + userId
);
430 return base
+ '/' + JcrUtils
.firstCharsToPath(userId
, 2) + '/' + userId
;
434 // /** Write remote roles used by remote access in the home directory */
435 // private void writeRemoteRoles(Node userHome, String[] roles)
436 // throws RepositoryException {
437 // boolean writeRoles = false;
438 // if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
439 // Value[] remoteRoles = userHome.getProperty(
440 // ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
441 // if (remoteRoles.length != roles.length)
442 // writeRoles = true;
444 // for (int i = 0; i < remoteRoles.length; i++)
445 // if (!remoteRoles[i].getString().equals(roles[i]))
446 // writeRoles = true;
448 // writeRoles = true;
451 // userHome.getSession().getWorkspace().getVersionManager()
452 // .checkout(userHome.getPath());
453 // userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roles);
454 // JcrUtils.updateLastModified(userHome);
455 // userHome.getSession().save();
456 // userHome.getSession().getWorkspace().getVersionManager()
457 // .checkin(userHome.getPath());
458 // if (log.isDebugEnabled())
459 // log.debug("Wrote remote roles " + roles + " for "
460 // + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));