1 package org
.argeo
.cms
.internal
.useradmin
.jackrabbit
;
3 import static org
.argeo
.cms
.KernelHeader
.ROLE_ADMIN
;
4 import static org
.argeo
.cms
.KernelHeader
.USERNAME_ADMIN
;
5 import static org
.argeo
.cms
.KernelHeader
.USERNAME_DEMO
;
7 import java
.util
.ArrayList
;
8 import java
.util
.Arrays
;
9 import java
.util
.Iterator
;
10 import java
.util
.LinkedHashSet
;
11 import java
.util
.List
;
14 import javax
.jcr
.Node
;
15 import javax
.jcr
.Repository
;
16 import javax
.jcr
.RepositoryException
;
17 import javax
.jcr
.Session
;
18 import javax
.jcr
.SimpleCredentials
;
19 import javax
.jcr
.version
.VersionManager
;
21 import org
.apache
.jackrabbit
.api
.JackrabbitSession
;
22 import org
.apache
.jackrabbit
.api
.security
.user
.Authorizable
;
23 import org
.apache
.jackrabbit
.api
.security
.user
.Group
;
24 import org
.apache
.jackrabbit
.api
.security
.user
.User
;
25 import org
.apache
.jackrabbit
.api
.security
.user
.UserManager
;
26 import org
.apache
.jackrabbit
.core
.security
.authentication
.CryptedSimpleCredentials
;
27 import org
.apache
.jackrabbit
.core
.security
.user
.UserAccessControlProvider
;
28 import org
.argeo
.ArgeoException
;
29 import org
.argeo
.cms
.CmsException
;
30 import org
.argeo
.cms
.KernelHeader
;
31 import org
.argeo
.cms
.internal
.auth
.GrantedAuthorityPrincipal
;
32 import org
.argeo
.cms
.internal
.auth
.JcrSecurityModel
;
33 import org
.argeo
.jcr
.JcrUtils
;
34 import org
.argeo
.jcr
.UserJcrUtils
;
35 import org
.argeo
.security
.NodeAuthenticationToken
;
36 import org
.argeo
.security
.SecurityUtils
;
37 import org
.argeo
.security
.UserAdminService
;
38 import org
.argeo
.security
.jcr
.JcrUserDetails
;
39 import org
.argeo
.security
.jcr
.NewUserDetails
;
40 import org
.springframework
.dao
.DataAccessException
;
41 import org
.springframework
.security
.authentication
.AuthenticationProvider
;
42 import org
.springframework
.security
.authentication
.BadCredentialsException
;
43 import org
.springframework
.security
.authentication
.UsernamePasswordAuthenticationToken
;
44 import org
.springframework
.security
.core
.Authentication
;
45 import org
.springframework
.security
.core
.AuthenticationException
;
46 import org
.springframework
.security
.core
.GrantedAuthority
;
47 import org
.springframework
.security
.core
.context
.SecurityContextHolder
;
48 import org
.springframework
.security
.core
.userdetails
.UserDetails
;
49 import org
.springframework
.security
.core
.userdetails
.UsernameNotFoundException
;
52 * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
53 * implementation. Roles are implemented with Groups.
55 public class JackrabbitUserAdminService
implements UserAdminService
,
56 AuthenticationProvider
{
57 private final static String JACKR_ADMINISTRATORS
= "administrators";
58 private final static String REP_PRINCIPAL_NAME
= "rep:principalName";
59 // private final static String REP_PASSWORD = "rep:password";
61 private Repository repository
;
62 private JcrSecurityModel securityModel
;
64 private JackrabbitSession adminSession
= null;
66 private String initialPassword
= "demo";
68 public void init() throws RepositoryException
{
69 Authentication authentication
= SecurityContextHolder
.getContext()
71 authentication
.getName();
72 adminSession
= (JackrabbitSession
) repository
.login();
73 Authorizable adminGroup
= getUserManager().getAuthorizable(ROLE_ADMIN
);
74 if (adminGroup
== null) {
75 adminGroup
= getUserManager().createGroup(ROLE_ADMIN
);
80 Authorizable superUser
= getUserManager().getAuthorizable(
82 if (superUser
== null) {
83 superUser
= getUserManager().createUser(USERNAME_ADMIN
,
85 ((Group
) adminGroup
).addMember(superUser
);
86 securityModel
.sync(adminSession
, USERNAME_ADMIN
, null);
89 // create demo user only at initialisation
90 Authorizable demoUser
= getUserManager().getAuthorizable(
93 throw new CmsException("There is already a demo user");
94 demoUser
= getUserManager().createUser(USERNAME_DEMO
,
96 securityModel
.sync(adminSession
, USERNAME_DEMO
, null);
99 securityModel
.init(adminSession
);
102 public void destroy() throws RepositoryException
{
103 JcrUtils
.logoutQuietly(adminSession
);
106 private UserManager
getUserManager() throws RepositoryException
{
107 return adminSession
.getUserManager();
111 public void createUser(UserDetails user
) {
113 // if (getUserManager().getAuthorizable(user.getUsername()) == null)
115 getUserManager().createUser(user
.getUsername(), user
.getPassword());
116 Node userProfile
= securityModel
.sync(adminSession
,
117 user
.getUsername(), null);
118 if (user
instanceof NewUserDetails
)
119 ((NewUserDetails
) user
).mapToProfileNode(userProfile
);
120 userProfile
.getSession().save();
123 VersionManager versionManager
= userProfile
.getSession()
124 .getWorkspace().getVersionManager();
125 if (versionManager
.isCheckedOut(userProfile
.getPath()))
126 versionManager
.checkin(userProfile
.getPath());
129 } catch (RepositoryException e
) {
130 throw new ArgeoException("Cannot create user " + user
, e
);
135 public void updateUser(UserDetails userDetails
) {
137 String username
= userDetails
.getUsername();
138 User user
= (User
) getUserManager().getAuthorizable(username
);
140 throw new ArgeoException("No user " + userDetails
.getUsername());
143 String newPassword
= userDetails
.getPassword();
144 if (!newPassword
.trim().equals("")) {
145 if (newPassword
.startsWith("{SHA-256}")) {
146 // Already hashed password
147 throw new CmsException("Cannot import hashed password");
148 // Value v = adminSession.getValueFactory().createValue(
150 // user.setProperty(REP_PASSWORD, v);
151 // TODO find a way to deal w/ protected property
153 // http://jackrabbit.apache.org/api/2.2/org/apache/jackrabbit/core/security/user/UserImporter.html
155 SimpleCredentials sp
= new SimpleCredentials(
156 userDetails
.getUsername(),
157 newPassword
.toCharArray());
158 CryptedSimpleCredentials credentials
= (CryptedSimpleCredentials
) user
161 if (!credentials
.matches(sp
))
162 user
.changePassword(new String(newPassword
));
166 List
<String
> roles
= new ArrayList
<String
>();
167 for (GrantedAuthority ga
: userDetails
.getAuthorities()) {
168 if (ga
.getAuthority().equals(KernelHeader
.ROLE_USER
))
170 roles
.add(ga
.getAuthority());
173 groups
: for (Iterator
<Group
> it
= user
.memberOf(); it
.hasNext();) {
174 Group group
= it
.next();
175 String groupName
= group
.getPrincipal().getName();
176 String role
= groupNameToRole(groupName
);
180 if (roles
.contains(role
))
183 group
.removeMember(user
);
184 if (role
.equals(KernelHeader
.ROLE_ADMIN
)) {
185 Group administratorsGroup
= ((Group
) getUserManager()
186 .getAuthorizable(JACKR_ADMINISTRATORS
));
187 if (administratorsGroup
.isDeclaredMember(user
))
188 administratorsGroup
.removeMember(user
);
193 // remaining (new memberships)
194 for (String role
: roles
) {
195 String groupName
= roleToGroupName(role
);
196 Group group
= (Group
) getUserManager().getAuthorizable(
199 throw new ArgeoException("Group " + role
201 + " whereas it was granted to user " + userDetails
);
202 group
.addMember(user
);
204 // add to Jackrabbit administrators
205 if (role
.equals(KernelHeader
.ROLE_ADMIN
)) {
206 Group administratorsGroup
= (Group
) getUserManager()
207 .getAuthorizable(JACKR_ADMINISTRATORS
);
208 administratorsGroup
.addMember(user
);
212 } catch (Exception e
) {
213 throw new ArgeoException("Cannot update user details", e
);
219 public void deleteUser(String username
) {
221 getUserManager().getAuthorizable(username
).remove();
222 } catch (RepositoryException e
) {
223 throw new ArgeoException("Cannot remove user " + username
, e
);
228 public void changePassword(String oldPassword
, String newPassword
) {
229 Authentication authentication
= SecurityContextHolder
.getContext()
230 .getAuthentication();
231 String username
= authentication
.getName();
233 SimpleCredentials sp
= new SimpleCredentials(username
,
234 oldPassword
.toCharArray());
235 User user
= (User
) getUserManager().getAuthorizable(username
);
236 CryptedSimpleCredentials credentials
= (CryptedSimpleCredentials
) user
238 if (credentials
.matches(sp
))
239 user
.changePassword(newPassword
);
241 throw new BadCredentialsException("Bad credentials provided");
242 } catch (Exception e
) {
243 throw new ArgeoException("Cannot change password for user "
249 public boolean userExists(String username
) {
251 Authorizable authorizable
= getUserManager().getAuthorizable(
253 if (authorizable
!= null && authorizable
instanceof User
)
256 } catch (RepositoryException e
) {
257 throw new ArgeoException("Cannot check whether user " + username
263 public Set
<String
> listUsers() {
264 LinkedHashSet
<String
> res
= new LinkedHashSet
<String
>();
266 Iterator
<Authorizable
> users
= getUserManager().findAuthorizables(
267 "rep:principalName", null, UserManager
.SEARCH_TYPE_USER
);
268 while (users
.hasNext()) {
269 res
.add(users
.next().getPrincipal().getName());
272 } catch (RepositoryException e
) {
273 throw new ArgeoException("Cannot list users", e
);
278 public Set
<String
> listUsersInRole(String role
) {
279 LinkedHashSet
<String
> res
= new LinkedHashSet
<String
>();
281 Group group
= (Group
) getUserManager().getAuthorizable(role
);
282 Iterator
<Authorizable
> users
= group
.getMembers();
284 while (users
.hasNext()) {
285 res
.add(users
.next().getPrincipal().getName());
288 } catch (RepositoryException e
) {
289 throw new ArgeoException("Cannot list users in role " + role
, e
);
294 public void synchronize() {
298 public void newRole(String role
) {
300 getUserManager().createGroup(role
);
301 } catch (RepositoryException e
) {
302 throw new ArgeoException("Cannot create role " + role
, e
);
307 public Set
<String
> listEditableRoles() {
308 LinkedHashSet
<String
> res
= new LinkedHashSet
<String
>();
310 Iterator
<Authorizable
> groups
= getUserManager().findAuthorizables(
311 REP_PRINCIPAL_NAME
, null, UserManager
.SEARCH_TYPE_GROUP
);
312 while (groups
.hasNext()) {
313 Group group
= (Group
) groups
.next();
314 String groupName
= group
.getPrincipal().getName();
315 String role
= groupNameToRole(groupName
);
317 && !role
.equals(KernelHeader
.ROLE_GROUP_ADMIN
)
318 && !(role
.equals(KernelHeader
.ROLE_ADMIN
) && !SecurityUtils
319 .hasCurrentThreadAuthority(KernelHeader
.ROLE_ADMIN
)))
323 } catch (RepositoryException e
) {
324 throw new ArgeoException("Cannot list groups", e
);
329 public void deleteRole(String role
) {
331 getUserManager().getAuthorizable(role
).remove();
332 } catch (RepositoryException e
) {
333 throw new ArgeoException("Cannot remove role " + role
, e
);
337 protected String
roleToGroupName(String role
) {
339 if (role
.equals(KernelHeader
.ROLE_USER_ADMIN
))
340 groupName
= UserAccessControlProvider
.USER_ADMIN_GROUP_NAME
;
341 else if (role
.equals(KernelHeader
.ROLE_GROUP_ADMIN
))
342 groupName
= UserAccessControlProvider
.GROUP_ADMIN_GROUP_NAME
;
348 protected String
groupNameToRole(String groupName
) {
350 if (groupName
.equals(UserAccessControlProvider
.USER_ADMIN_GROUP_NAME
)) {
351 role
= KernelHeader
.ROLE_USER_ADMIN
;
353 .equals(UserAccessControlProvider
.GROUP_ADMIN_GROUP_NAME
)) {
354 role
= KernelHeader
.ROLE_GROUP_ADMIN
;
355 } else if (groupName
.equals(JACKR_ADMINISTRATORS
)) {
364 public UserDetails
loadUserByUsername(String username
)
365 throws UsernameNotFoundException
, DataAccessException
{
367 User user
= (User
) getUserManager().getAuthorizable(username
);
369 throw new UsernameNotFoundException("User " + username
370 + " cannot be found");
371 return loadJcrUserDetails(adminSession
, username
);
372 } catch (RepositoryException e
) {
373 throw new ArgeoException("Cannot load user " + username
, e
);
377 protected JcrUserDetails
loadJcrUserDetails(Session session
, String username
)
378 throws RepositoryException
{
379 if (username
== null)
380 username
= session
.getUserID();
381 User user
= (User
) getUserManager().getAuthorizable(username
);
383 ArrayList
<GrantedAuthorityPrincipal
> authorities
= new ArrayList
<GrantedAuthorityPrincipal
>();
384 authorities
.add(new GrantedAuthorityPrincipal(KernelHeader
.ROLE_USER
));
386 Group adminGroup
= (Group
) getUserManager().getAuthorizable(
387 KernelHeader
.ROLE_ADMIN
);
389 Iterator
<?
extends Authorizable
> groups
;
390 if (username
.equals(KernelHeader
.USERNAME_ADMIN
)
391 || adminGroup
.isDeclaredMember(user
)) {
392 groups
= getUserManager().findAuthorizables(REP_PRINCIPAL_NAME
,
393 null, UserManager
.SEARCH_TYPE_GROUP
);
395 groups
= user
.declaredMemberOf();
398 while (groups
.hasNext()) {
399 Authorizable group
= groups
.next();
400 String groupName
= group
.getPrincipal().getName();
401 String role
= groupNameToRole(groupName
);
403 authorities
.add(new GrantedAuthorityPrincipal(role
));
406 Node userProfile
= UserJcrUtils
.getUserProfile(session
, username
);
407 JcrUserDetails userDetails
= new JcrUserDetails(userProfile
, "",
412 // AUTHENTICATION PROVIDER
413 public synchronized Authentication
authenticate(
414 Authentication authentication
) throws AuthenticationException
{
415 NodeAuthenticationToken siteAuth
= (NodeAuthenticationToken
) authentication
;
416 String username
= siteAuth
.getName();
417 if (!(siteAuth
.getCredentials() instanceof char[]))
418 throw new ArgeoException("Only char array passwords are supported");
419 char[] password
= (char[]) siteAuth
.getCredentials();
421 SimpleCredentials sp
= new SimpleCredentials(siteAuth
.getName(),
423 User user
= (User
) getUserManager().getAuthorizable(username
);
425 throw new BadCredentialsException("Bad credentials");
426 CryptedSimpleCredentials credentials
= (CryptedSimpleCredentials
) user
428 // String providedPassword = siteAuth.getCredentials().toString();
429 if (!credentials
.matches(sp
))
430 throw new BadCredentialsException("Bad credentials");
432 // session = repository.login(sp, null);
434 Node userProfile
= UserJcrUtils
.getUserProfile(adminSession
,
436 JcrUserDetails
.checkAccountStatus(userProfile
);
437 } catch (BadCredentialsException e
) {
439 } catch (Exception e
) {
440 throw new BadCredentialsException(
441 "Cannot authenticate " + siteAuth
, e
);
443 Arrays
.fill(password
, '*');
447 JcrUserDetails userDetails
= loadJcrUserDetails(adminSession
,
449 NodeAuthenticationToken authenticated
= new NodeAuthenticationToken(
450 siteAuth
, userDetails
.getAuthorities());
451 authenticated
.setDetails(userDetails
);
452 return authenticated
;
453 } catch (RepositoryException e
) {
454 throw new ArgeoException(
455 "Unexpected exception when authenticating " + siteAuth
, e
);
459 @SuppressWarnings("rawtypes")
460 public boolean supports(Class authentication
) {
461 return UsernamePasswordAuthenticationToken
.class
462 .isAssignableFrom(authentication
);
465 public void setRepository(Repository repository
) {
466 this.repository
= repository
;
469 public void setSecurityModel(JcrSecurityModel securityModel
) {
470 this.securityModel
= securityModel
;