1 package org
.argeo
.cms
.internal
.useradmin
.jackrabbit
;
3 import java
.util
.ArrayList
;
4 import java
.util
.Arrays
;
5 import java
.util
.Iterator
;
6 import java
.util
.LinkedHashSet
;
10 import javax
.jcr
.Node
;
11 import javax
.jcr
.Repository
;
12 import javax
.jcr
.RepositoryException
;
13 import javax
.jcr
.Session
;
14 import javax
.jcr
.SimpleCredentials
;
15 import javax
.jcr
.Value
;
16 import javax
.jcr
.version
.VersionManager
;
18 import org
.apache
.jackrabbit
.api
.JackrabbitSession
;
19 import org
.apache
.jackrabbit
.api
.security
.user
.Authorizable
;
20 import org
.apache
.jackrabbit
.api
.security
.user
.Group
;
21 import org
.apache
.jackrabbit
.api
.security
.user
.User
;
22 import org
.apache
.jackrabbit
.api
.security
.user
.UserManager
;
23 import org
.apache
.jackrabbit
.core
.security
.authentication
.CryptedSimpleCredentials
;
24 import org
.apache
.jackrabbit
.core
.security
.user
.UserAccessControlProvider
;
25 import org
.argeo
.ArgeoException
;
26 import org
.argeo
.cms
.CmsException
;
27 import org
.argeo
.cms
.KernelHeader
;
28 import org
.argeo
.cms
.internal
.auth
.GrantedAuthorityPrincipal
;
29 import org
.argeo
.cms
.internal
.auth
.JcrSecurityModel
;
30 import org
.argeo
.jcr
.JcrUtils
;
31 import org
.argeo
.jcr
.UserJcrUtils
;
32 import org
.argeo
.security
.NodeAuthenticationToken
;
33 import org
.argeo
.security
.SecurityUtils
;
34 import org
.argeo
.security
.UserAdminService
;
35 import org
.argeo
.security
.jcr
.JcrUserDetails
;
36 import org
.argeo
.security
.jcr
.NewUserDetails
;
37 import org
.springframework
.dao
.DataAccessException
;
38 import org
.springframework
.security
.authentication
.AuthenticationProvider
;
39 import org
.springframework
.security
.authentication
.BadCredentialsException
;
40 import org
.springframework
.security
.authentication
.UsernamePasswordAuthenticationToken
;
41 import org
.springframework
.security
.core
.Authentication
;
42 import org
.springframework
.security
.core
.AuthenticationException
;
43 import org
.springframework
.security
.core
.GrantedAuthority
;
44 import org
.springframework
.security
.core
.context
.SecurityContextHolder
;
45 import org
.springframework
.security
.core
.userdetails
.UserDetails
;
46 import org
.springframework
.security
.core
.userdetails
.UsernameNotFoundException
;
49 * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
50 * implementation. Roles are implemented with Groups.
52 public class JackrabbitUserAdminService
implements UserAdminService
,
53 AuthenticationProvider
{
54 private final static String JACKR_ADMINISTRATORS
= "administrators";
55 private final static String REP_PRINCIPAL_NAME
= "rep:principalName";
56 private final static String REP_PASSWORD
= "rep:password";
58 private Repository repository
;
59 private JcrSecurityModel securityModel
;
61 private JackrabbitSession adminSession
= null;
63 private String superUserInitialPassword
= "demo";
65 public void init() throws RepositoryException
{
66 Authentication authentication
= SecurityContextHolder
.getContext()
68 authentication
.getName();
69 adminSession
= (JackrabbitSession
) repository
.login();
70 Authorizable adminGroup
= getUserManager().getAuthorizable(
71 KernelHeader
.ROLE_ADMIN
);
72 if (adminGroup
== null) {
73 adminGroup
= getUserManager().createGroup(KernelHeader
.ROLE_ADMIN
);
76 Authorizable superUser
= getUserManager().getAuthorizable(
77 KernelHeader
.USERNAME_ADMIN
);
78 if (superUser
== null) {
79 superUser
= getUserManager().createUser(
80 KernelHeader
.USERNAME_ADMIN
, superUserInitialPassword
);
81 ((Group
) adminGroup
).addMember(superUser
);
82 securityModel
.sync(adminSession
, KernelHeader
.USERNAME_ADMIN
, null);
85 securityModel
.init(adminSession
);
88 public void destroy() throws RepositoryException
{
89 JcrUtils
.logoutQuietly(adminSession
);
92 private UserManager
getUserManager() throws RepositoryException
{
93 return adminSession
.getUserManager();
97 public void createUser(UserDetails user
) {
99 // if (getUserManager().getAuthorizable(user.getUsername()) == null)
101 getUserManager().createUser(user
.getUsername(), user
.getPassword());
102 Node userProfile
= securityModel
.sync(adminSession
,
103 user
.getUsername(), null);
104 if (user
instanceof NewUserDetails
)
105 ((NewUserDetails
) user
).mapToProfileNode(userProfile
);
106 userProfile
.getSession().save();
109 VersionManager versionManager
= userProfile
.getSession()
110 .getWorkspace().getVersionManager();
111 if (versionManager
.isCheckedOut(userProfile
.getPath()))
112 versionManager
.checkin(userProfile
.getPath());
115 } catch (RepositoryException e
) {
116 throw new ArgeoException("Cannot create user " + user
, e
);
121 public void updateUser(UserDetails userDetails
) {
123 String username
= userDetails
.getUsername();
124 User user
= (User
) getUserManager().getAuthorizable(username
);
126 throw new ArgeoException("No user " + userDetails
.getUsername());
129 String newPassword
= userDetails
.getPassword();
130 if (!newPassword
.trim().equals("")) {
131 if (newPassword
.startsWith("{SHA-256}")) {
132 // Already hashed password
133 throw new CmsException("Cannot import hashed password");
134 // Value v = adminSession.getValueFactory().createValue(
136 // user.setProperty(REP_PASSWORD, v);
137 // TODO find a way to deal w/ protected property
139 // http://jackrabbit.apache.org/api/2.2/org/apache/jackrabbit/core/security/user/UserImporter.html
141 SimpleCredentials sp
= new SimpleCredentials(
142 userDetails
.getUsername(),
143 newPassword
.toCharArray());
144 CryptedSimpleCredentials credentials
= (CryptedSimpleCredentials
) user
147 if (!credentials
.matches(sp
))
148 user
.changePassword(new String(newPassword
));
152 List
<String
> roles
= new ArrayList
<String
>();
153 for (GrantedAuthority ga
: userDetails
.getAuthorities()) {
154 if (ga
.getAuthority().equals(KernelHeader
.ROLE_USER
))
156 roles
.add(ga
.getAuthority());
159 groups
: for (Iterator
<Group
> it
= user
.memberOf(); it
.hasNext();) {
160 Group group
= it
.next();
161 String groupName
= group
.getPrincipal().getName();
162 String role
= groupNameToRole(groupName
);
166 if (roles
.contains(role
))
169 group
.removeMember(user
);
170 if (role
.equals(KernelHeader
.ROLE_ADMIN
)) {
171 Group administratorsGroup
= ((Group
) getUserManager()
172 .getAuthorizable(JACKR_ADMINISTRATORS
));
173 if (administratorsGroup
.isDeclaredMember(user
))
174 administratorsGroup
.removeMember(user
);
179 // remaining (new memberships)
180 for (String role
: roles
) {
181 String groupName
= roleToGroupName(role
);
182 Group group
= (Group
) getUserManager().getAuthorizable(
185 throw new ArgeoException("Group " + role
187 + " whereas it was granted to user " + userDetails
);
188 group
.addMember(user
);
190 // add to Jackrabbit administrators
191 if (role
.equals(KernelHeader
.ROLE_ADMIN
)) {
192 Group administratorsGroup
= (Group
) getUserManager()
193 .getAuthorizable(JACKR_ADMINISTRATORS
);
194 administratorsGroup
.addMember(user
);
198 } catch (Exception e
) {
199 throw new ArgeoException("Cannot update user details", e
);
205 public void deleteUser(String username
) {
207 getUserManager().getAuthorizable(username
).remove();
208 } catch (RepositoryException e
) {
209 throw new ArgeoException("Cannot remove user " + username
, e
);
214 public void changePassword(String oldPassword
, String newPassword
) {
215 Authentication authentication
= SecurityContextHolder
.getContext()
216 .getAuthentication();
217 String username
= authentication
.getName();
219 SimpleCredentials sp
= new SimpleCredentials(username
,
220 oldPassword
.toCharArray());
221 User user
= (User
) getUserManager().getAuthorizable(username
);
222 CryptedSimpleCredentials credentials
= (CryptedSimpleCredentials
) user
224 if (credentials
.matches(sp
))
225 user
.changePassword(newPassword
);
227 throw new BadCredentialsException("Bad credentials provided");
228 } catch (Exception e
) {
229 throw new ArgeoException("Cannot change password for user "
235 public boolean userExists(String username
) {
237 Authorizable authorizable
= getUserManager().getAuthorizable(
239 if (authorizable
!= null && authorizable
instanceof User
)
242 } catch (RepositoryException e
) {
243 throw new ArgeoException("Cannot check whether user " + username
249 public Set
<String
> listUsers() {
250 LinkedHashSet
<String
> res
= new LinkedHashSet
<String
>();
252 Iterator
<Authorizable
> users
= getUserManager().findAuthorizables(
253 "rep:principalName", null, UserManager
.SEARCH_TYPE_USER
);
254 while (users
.hasNext()) {
255 res
.add(users
.next().getPrincipal().getName());
258 } catch (RepositoryException e
) {
259 throw new ArgeoException("Cannot list users", e
);
264 public Set
<String
> listUsersInRole(String role
) {
265 LinkedHashSet
<String
> res
= new LinkedHashSet
<String
>();
267 Group group
= (Group
) getUserManager().getAuthorizable(role
);
268 Iterator
<Authorizable
> users
= group
.getMembers();
270 while (users
.hasNext()) {
271 res
.add(users
.next().getPrincipal().getName());
274 } catch (RepositoryException e
) {
275 throw new ArgeoException("Cannot list users in role " + role
, e
);
280 public void synchronize() {
284 public void newRole(String role
) {
286 getUserManager().createGroup(role
);
287 } catch (RepositoryException e
) {
288 throw new ArgeoException("Cannot create role " + role
, e
);
293 public Set
<String
> listEditableRoles() {
294 LinkedHashSet
<String
> res
= new LinkedHashSet
<String
>();
296 Iterator
<Authorizable
> groups
= getUserManager().findAuthorizables(
297 REP_PRINCIPAL_NAME
, null, UserManager
.SEARCH_TYPE_GROUP
);
298 while (groups
.hasNext()) {
299 Group group
= (Group
) groups
.next();
300 String groupName
= group
.getPrincipal().getName();
301 String role
= groupNameToRole(groupName
);
303 && !role
.equals(KernelHeader
.ROLE_GROUP_ADMIN
)
304 && !(role
.equals(KernelHeader
.ROLE_ADMIN
) && !SecurityUtils
305 .hasCurrentThreadAuthority(KernelHeader
.ROLE_ADMIN
)))
309 } catch (RepositoryException e
) {
310 throw new ArgeoException("Cannot list groups", e
);
315 public void deleteRole(String role
) {
317 getUserManager().getAuthorizable(role
).remove();
318 } catch (RepositoryException e
) {
319 throw new ArgeoException("Cannot remove role " + role
, e
);
323 protected String
roleToGroupName(String role
) {
325 if (role
.equals(KernelHeader
.ROLE_USER_ADMIN
))
326 groupName
= UserAccessControlProvider
.USER_ADMIN_GROUP_NAME
;
327 else if (role
.equals(KernelHeader
.ROLE_GROUP_ADMIN
))
328 groupName
= UserAccessControlProvider
.GROUP_ADMIN_GROUP_NAME
;
334 protected String
groupNameToRole(String groupName
) {
336 if (groupName
.equals(UserAccessControlProvider
.USER_ADMIN_GROUP_NAME
)) {
337 role
= KernelHeader
.ROLE_USER_ADMIN
;
339 .equals(UserAccessControlProvider
.GROUP_ADMIN_GROUP_NAME
)) {
340 role
= KernelHeader
.ROLE_GROUP_ADMIN
;
341 } else if (groupName
.equals(JACKR_ADMINISTRATORS
)) {
350 public UserDetails
loadUserByUsername(String username
)
351 throws UsernameNotFoundException
, DataAccessException
{
353 User user
= (User
) getUserManager().getAuthorizable(username
);
355 throw new UsernameNotFoundException("User " + username
356 + " cannot be found");
357 return loadJcrUserDetails(adminSession
, username
);
358 } catch (RepositoryException e
) {
359 throw new ArgeoException("Cannot load user " + username
, e
);
363 protected JcrUserDetails
loadJcrUserDetails(Session session
, String username
)
364 throws RepositoryException
{
365 if (username
== null)
366 username
= session
.getUserID();
367 User user
= (User
) getUserManager().getAuthorizable(username
);
369 ArrayList
<GrantedAuthorityPrincipal
> authorities
= new ArrayList
<GrantedAuthorityPrincipal
>();
370 authorities
.add(new GrantedAuthorityPrincipal(KernelHeader
.ROLE_USER
));
372 Group adminGroup
= (Group
) getUserManager().getAuthorizable(
373 KernelHeader
.ROLE_ADMIN
);
375 Iterator
<?
extends Authorizable
> groups
;
376 if (username
.equals(KernelHeader
.USERNAME_ADMIN
)
377 || adminGroup
.isDeclaredMember(user
)) {
378 groups
= getUserManager().findAuthorizables(REP_PRINCIPAL_NAME
,
379 null, UserManager
.SEARCH_TYPE_GROUP
);
381 groups
= user
.declaredMemberOf();
384 while (groups
.hasNext()) {
385 Authorizable group
= groups
.next();
386 String groupName
= group
.getPrincipal().getName();
387 String role
= groupNameToRole(groupName
);
389 authorities
.add(new GrantedAuthorityPrincipal(role
));
392 Node userProfile
= UserJcrUtils
.getUserProfile(session
, username
);
393 JcrUserDetails userDetails
= new JcrUserDetails(userProfile
, "",
398 // AUTHENTICATION PROVIDER
399 public synchronized Authentication
authenticate(
400 Authentication authentication
) throws AuthenticationException
{
401 NodeAuthenticationToken siteAuth
= (NodeAuthenticationToken
) authentication
;
402 String username
= siteAuth
.getName();
403 if (!(siteAuth
.getCredentials() instanceof char[]))
404 throw new ArgeoException("Only char array passwords are supported");
405 char[] password
= (char[]) siteAuth
.getCredentials();
407 SimpleCredentials sp
= new SimpleCredentials(siteAuth
.getName(),
409 User user
= (User
) getUserManager().getAuthorizable(username
);
411 throw new BadCredentialsException("Bad credentials");
412 CryptedSimpleCredentials credentials
= (CryptedSimpleCredentials
) user
414 // String providedPassword = siteAuth.getCredentials().toString();
415 if (!credentials
.matches(sp
))
416 throw new BadCredentialsException("Bad credentials");
418 // session = repository.login(sp, null);
420 Node userProfile
= UserJcrUtils
.getUserProfile(adminSession
,
422 JcrUserDetails
.checkAccountStatus(userProfile
);
423 } catch (BadCredentialsException e
) {
425 } catch (Exception e
) {
426 throw new BadCredentialsException(
427 "Cannot authenticate " + siteAuth
, e
);
429 Arrays
.fill(password
, '*');
433 JcrUserDetails userDetails
= loadJcrUserDetails(adminSession
,
435 NodeAuthenticationToken authenticated
= new NodeAuthenticationToken(
436 siteAuth
, userDetails
.getAuthorities());
437 authenticated
.setDetails(userDetails
);
438 return authenticated
;
439 } catch (RepositoryException e
) {
440 throw new ArgeoException(
441 "Unexpected exception when authenticating " + siteAuth
, e
);
445 @SuppressWarnings("rawtypes")
446 public boolean supports(Class authentication
) {
447 return UsernamePasswordAuthenticationToken
.class
448 .isAssignableFrom(authentication
);
451 public void setRepository(Repository repository
) {
452 this.repository
= repository
;
455 public void setSecurityModel(JcrSecurityModel securityModel
) {
456 this.securityModel
= securityModel
;