]> git.argeo.org Git - lgpl/argeo-commons.git/blob - JackrabbitUserAdminService.java
cc6d85b48587c4453b3cd00836860f9cff6881d5
[lgpl/argeo-commons.git] / JackrabbitUserAdminService.java
1 package org.argeo.cms.internal.useradmin.jackrabbit;
2
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;
6
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;
12 import java.util.Set;
13
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;
20
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;
50
51 /**
52 * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
53 * implementation. Roles are implemented with Groups.
54 */
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";
60
61 private Repository repository;
62 private JcrSecurityModel securityModel;
63
64 private JackrabbitSession adminSession = null;
65
66 private String initialPassword = "demo";
67
68 public void init() throws RepositoryException {
69 Authentication authentication = SecurityContextHolder.getContext()
70 .getAuthentication();
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);
76 adminSession.save();
77 }
78
79 // create superuser
80 Authorizable superUser = getUserManager().getAuthorizable(
81 USERNAME_ADMIN);
82 if (superUser == null) {
83 superUser = getUserManager().createUser(USERNAME_ADMIN,
84 initialPassword);
85 ((Group) adminGroup).addMember(superUser);
86 securityModel.sync(adminSession, USERNAME_ADMIN, null);
87 adminSession.save();
88
89 // create demo user only at initialisation
90 Authorizable demoUser = getUserManager().getAuthorizable(
91 USERNAME_DEMO);
92 if (demoUser != null)
93 throw new CmsException("There is already a demo user");
94 demoUser = getUserManager().createUser(USERNAME_DEMO,
95 initialPassword);
96 securityModel.sync(adminSession, USERNAME_DEMO, null);
97 adminSession.save();
98 }
99 securityModel.init(adminSession);
100 }
101
102 public void destroy() throws RepositoryException {
103 JcrUtils.logoutQuietly(adminSession);
104 }
105
106 private UserManager getUserManager() throws RepositoryException {
107 return adminSession.getUserManager();
108 }
109
110 @Override
111 public void createUser(UserDetails user) {
112 try {
113 // if (getUserManager().getAuthorizable(user.getUsername()) == null)
114 // {
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();
121
122 // check in node
123 VersionManager versionManager = userProfile.getSession()
124 .getWorkspace().getVersionManager();
125 if (versionManager.isCheckedOut(userProfile.getPath()))
126 versionManager.checkin(userProfile.getPath());
127 // }
128 updateUser(user);
129 } catch (RepositoryException e) {
130 throw new ArgeoException("Cannot create user " + user, e);
131 }
132 }
133
134 @Override
135 public void updateUser(UserDetails userDetails) {
136 try {
137 String username = userDetails.getUsername();
138 User user = (User) getUserManager().getAuthorizable(username);
139 if (user == null)
140 throw new ArgeoException("No user " + userDetails.getUsername());
141
142 // new password
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(
149 // newPassword);
150 // user.setProperty(REP_PASSWORD, v);
151 // TODO find a way to deal w/ protected property
152 // see
153 // http://jackrabbit.apache.org/api/2.2/org/apache/jackrabbit/core/security/user/UserImporter.html
154 } else {
155 SimpleCredentials sp = new SimpleCredentials(
156 userDetails.getUsername(),
157 newPassword.toCharArray());
158 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
159 .getCredentials();
160
161 if (!credentials.matches(sp))
162 user.changePassword(new String(newPassword));
163 }
164 }
165
166 List<String> roles = new ArrayList<String>();
167 for (GrantedAuthority ga : userDetails.getAuthorities()) {
168 if (ga.getAuthority().equals(KernelHeader.ROLE_USER))
169 continue;
170 roles.add(ga.getAuthority());
171 }
172
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);
177 if (role == null)
178 continue groups;
179
180 if (roles.contains(role))
181 roles.remove(role);
182 else {
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);
189 }
190 }
191 }
192
193 // remaining (new memberships)
194 for (String role : roles) {
195 String groupName = roleToGroupName(role);
196 Group group = (Group) getUserManager().getAuthorizable(
197 groupName);
198 if (group == null)
199 throw new ArgeoException("Group " + role
200 + " does not exist,"
201 + " whereas it was granted to user " + userDetails);
202 group.addMember(user);
203
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);
209 }
210
211 }
212 } catch (Exception e) {
213 throw new ArgeoException("Cannot update user details", e);
214 }
215
216 }
217
218 @Override
219 public void deleteUser(String username) {
220 try {
221 getUserManager().getAuthorizable(username).remove();
222 } catch (RepositoryException e) {
223 throw new ArgeoException("Cannot remove user " + username, e);
224 }
225 }
226
227 @Override
228 public void changePassword(String oldPassword, String newPassword) {
229 Authentication authentication = SecurityContextHolder.getContext()
230 .getAuthentication();
231 String username = authentication.getName();
232 try {
233 SimpleCredentials sp = new SimpleCredentials(username,
234 oldPassword.toCharArray());
235 User user = (User) getUserManager().getAuthorizable(username);
236 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
237 .getCredentials();
238 if (credentials.matches(sp))
239 user.changePassword(newPassword);
240 else
241 throw new BadCredentialsException("Bad credentials provided");
242 } catch (Exception e) {
243 throw new ArgeoException("Cannot change password for user "
244 + username, e);
245 }
246 }
247
248 @Override
249 public boolean userExists(String username) {
250 try {
251 Authorizable authorizable = getUserManager().getAuthorizable(
252 username);
253 if (authorizable != null && authorizable instanceof User)
254 return true;
255 return false;
256 } catch (RepositoryException e) {
257 throw new ArgeoException("Cannot check whether user " + username
258 + " exists ", e);
259 }
260 }
261
262 @Override
263 public Set<String> listUsers() {
264 LinkedHashSet<String> res = new LinkedHashSet<String>();
265 try {
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());
270 }
271 return res;
272 } catch (RepositoryException e) {
273 throw new ArgeoException("Cannot list users", e);
274 }
275 }
276
277 @Override
278 public Set<String> listUsersInRole(String role) {
279 LinkedHashSet<String> res = new LinkedHashSet<String>();
280 try {
281 Group group = (Group) getUserManager().getAuthorizable(role);
282 Iterator<Authorizable> users = group.getMembers();
283 // NB: not recursive
284 while (users.hasNext()) {
285 res.add(users.next().getPrincipal().getName());
286 }
287 return res;
288 } catch (RepositoryException e) {
289 throw new ArgeoException("Cannot list users in role " + role, e);
290 }
291 }
292
293 @Override
294 public void synchronize() {
295 }
296
297 @Override
298 public void newRole(String role) {
299 try {
300 getUserManager().createGroup(role);
301 } catch (RepositoryException e) {
302 throw new ArgeoException("Cannot create role " + role, e);
303 }
304 }
305
306 @Override
307 public Set<String> listEditableRoles() {
308 LinkedHashSet<String> res = new LinkedHashSet<String>();
309 try {
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);
316 if (role != null
317 && !role.equals(KernelHeader.ROLE_GROUP_ADMIN)
318 && !(role.equals(KernelHeader.ROLE_ADMIN) && !SecurityUtils
319 .hasCurrentThreadAuthority(KernelHeader.ROLE_ADMIN)))
320 res.add(role);
321 }
322 return res;
323 } catch (RepositoryException e) {
324 throw new ArgeoException("Cannot list groups", e);
325 }
326 }
327
328 @Override
329 public void deleteRole(String role) {
330 try {
331 getUserManager().getAuthorizable(role).remove();
332 } catch (RepositoryException e) {
333 throw new ArgeoException("Cannot remove role " + role, e);
334 }
335 }
336
337 protected String roleToGroupName(String role) {
338 String groupName;
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;
343 else
344 groupName = role;
345 return groupName;
346 }
347
348 protected String groupNameToRole(String groupName) {
349 String role;
350 if (groupName.equals(UserAccessControlProvider.USER_ADMIN_GROUP_NAME)) {
351 role = KernelHeader.ROLE_USER_ADMIN;
352 } else if (groupName
353 .equals(UserAccessControlProvider.GROUP_ADMIN_GROUP_NAME)) {
354 role = KernelHeader.ROLE_GROUP_ADMIN;
355 } else if (groupName.equals(JACKR_ADMINISTRATORS)) {
356 return null;
357 } else {
358 role = groupName;
359 }
360 return role;
361 }
362
363 @Override
364 public UserDetails loadUserByUsername(String username)
365 throws UsernameNotFoundException, DataAccessException {
366 try {
367 User user = (User) getUserManager().getAuthorizable(username);
368 if (user == null)
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);
374 }
375 }
376
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);
382
383 ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
384 authorities.add(new GrantedAuthorityPrincipal(KernelHeader.ROLE_USER));
385
386 Group adminGroup = (Group) getUserManager().getAuthorizable(
387 KernelHeader.ROLE_ADMIN);
388
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);
394 } else {
395 groups = user.declaredMemberOf();
396 }
397
398 while (groups.hasNext()) {
399 Authorizable group = groups.next();
400 String groupName = group.getPrincipal().getName();
401 String role = groupNameToRole(groupName);
402 if (role != null)
403 authorities.add(new GrantedAuthorityPrincipal(role));
404 }
405
406 Node userProfile = UserJcrUtils.getUserProfile(session, username);
407 JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
408 authorities);
409 return userDetails;
410 }
411
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();
420 try {
421 SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
422 password);
423 User user = (User) getUserManager().getAuthorizable(username);
424 if (user == null)
425 throw new BadCredentialsException("Bad credentials");
426 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
427 .getCredentials();
428 // String providedPassword = siteAuth.getCredentials().toString();
429 if (!credentials.matches(sp))
430 throw new BadCredentialsException("Bad credentials");
431
432 // session = repository.login(sp, null);
433
434 Node userProfile = UserJcrUtils.getUserProfile(adminSession,
435 username);
436 JcrUserDetails.checkAccountStatus(userProfile);
437 } catch (BadCredentialsException e) {
438 throw e;
439 } catch (Exception e) {
440 throw new BadCredentialsException(
441 "Cannot authenticate " + siteAuth, e);
442 } finally {
443 Arrays.fill(password, '*');
444 }
445
446 try {
447 JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
448 username);
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);
456 }
457 }
458
459 @SuppressWarnings("rawtypes")
460 public boolean supports(Class authentication) {
461 return UsernamePasswordAuthenticationToken.class
462 .isAssignableFrom(authentication);
463 }
464
465 public void setRepository(Repository repository) {
466 this.repository = repository;
467 }
468
469 public void setSecurityModel(JcrSecurityModel securityModel) {
470 this.securityModel = securityModel;
471 }
472
473 }