]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitUserAdminService.java
a363515c9a716f540268f7bd496a7e456b0d1820
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / useradmin / jackrabbit / JackrabbitUserAdminService.java
1 package org.argeo.cms.internal.useradmin.jackrabbit;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Iterator;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Set;
9
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;
17
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;
47
48 /**
49 * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
50 * implementation. Roles are implemented with Groups.
51 */
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";
57
58 private Repository repository;
59 private JcrSecurityModel securityModel;
60
61 private JackrabbitSession adminSession = null;
62
63 private String superUserInitialPassword = "demo";
64
65 public void init() throws RepositoryException {
66 Authentication authentication = SecurityContextHolder.getContext()
67 .getAuthentication();
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);
74 adminSession.save();
75 }
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);
83 adminSession.save();
84 }
85 securityModel.init(adminSession);
86 }
87
88 public void destroy() throws RepositoryException {
89 JcrUtils.logoutQuietly(adminSession);
90 }
91
92 private UserManager getUserManager() throws RepositoryException {
93 return adminSession.getUserManager();
94 }
95
96 @Override
97 public void createUser(UserDetails user) {
98 try {
99 // if (getUserManager().getAuthorizable(user.getUsername()) == null)
100 // {
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();
107
108 // check in node
109 VersionManager versionManager = userProfile.getSession()
110 .getWorkspace().getVersionManager();
111 if (versionManager.isCheckedOut(userProfile.getPath()))
112 versionManager.checkin(userProfile.getPath());
113 // }
114 updateUser(user);
115 } catch (RepositoryException e) {
116 throw new ArgeoException("Cannot create user " + user, e);
117 }
118 }
119
120 @Override
121 public void updateUser(UserDetails userDetails) {
122 try {
123 String username = userDetails.getUsername();
124 User user = (User) getUserManager().getAuthorizable(username);
125 if (user == null)
126 throw new ArgeoException("No user " + userDetails.getUsername());
127
128 // new password
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(
135 // newPassword);
136 // user.setProperty(REP_PASSWORD, v);
137 // TODO find a way to deal w/ protected property
138 // see
139 // http://jackrabbit.apache.org/api/2.2/org/apache/jackrabbit/core/security/user/UserImporter.html
140 } else {
141 SimpleCredentials sp = new SimpleCredentials(
142 userDetails.getUsername(),
143 newPassword.toCharArray());
144 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
145 .getCredentials();
146
147 if (!credentials.matches(sp))
148 user.changePassword(new String(newPassword));
149 }
150 }
151
152 List<String> roles = new ArrayList<String>();
153 for (GrantedAuthority ga : userDetails.getAuthorities()) {
154 if (ga.getAuthority().equals(KernelHeader.ROLE_USER))
155 continue;
156 roles.add(ga.getAuthority());
157 }
158
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);
163 if (role == null)
164 continue groups;
165
166 if (roles.contains(role))
167 roles.remove(role);
168 else {
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);
175 }
176 }
177 }
178
179 // remaining (new memberships)
180 for (String role : roles) {
181 String groupName = roleToGroupName(role);
182 Group group = (Group) getUserManager().getAuthorizable(
183 groupName);
184 if (group == null)
185 throw new ArgeoException("Group " + role
186 + " does not exist,"
187 + " whereas it was granted to user " + userDetails);
188 group.addMember(user);
189
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);
195 }
196
197 }
198 } catch (Exception e) {
199 throw new ArgeoException("Cannot update user details", e);
200 }
201
202 }
203
204 @Override
205 public void deleteUser(String username) {
206 try {
207 getUserManager().getAuthorizable(username).remove();
208 } catch (RepositoryException e) {
209 throw new ArgeoException("Cannot remove user " + username, e);
210 }
211 }
212
213 @Override
214 public void changePassword(String oldPassword, String newPassword) {
215 Authentication authentication = SecurityContextHolder.getContext()
216 .getAuthentication();
217 String username = authentication.getName();
218 try {
219 SimpleCredentials sp = new SimpleCredentials(username,
220 oldPassword.toCharArray());
221 User user = (User) getUserManager().getAuthorizable(username);
222 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
223 .getCredentials();
224 if (credentials.matches(sp))
225 user.changePassword(newPassword);
226 else
227 throw new BadCredentialsException("Bad credentials provided");
228 } catch (Exception e) {
229 throw new ArgeoException("Cannot change password for user "
230 + username, e);
231 }
232 }
233
234 @Override
235 public boolean userExists(String username) {
236 try {
237 Authorizable authorizable = getUserManager().getAuthorizable(
238 username);
239 if (authorizable != null && authorizable instanceof User)
240 return true;
241 return false;
242 } catch (RepositoryException e) {
243 throw new ArgeoException("Cannot check whether user " + username
244 + " exists ", e);
245 }
246 }
247
248 @Override
249 public Set<String> listUsers() {
250 LinkedHashSet<String> res = new LinkedHashSet<String>();
251 try {
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());
256 }
257 return res;
258 } catch (RepositoryException e) {
259 throw new ArgeoException("Cannot list users", e);
260 }
261 }
262
263 @Override
264 public Set<String> listUsersInRole(String role) {
265 LinkedHashSet<String> res = new LinkedHashSet<String>();
266 try {
267 Group group = (Group) getUserManager().getAuthorizable(role);
268 Iterator<Authorizable> users = group.getMembers();
269 // NB: not recursive
270 while (users.hasNext()) {
271 res.add(users.next().getPrincipal().getName());
272 }
273 return res;
274 } catch (RepositoryException e) {
275 throw new ArgeoException("Cannot list users in role " + role, e);
276 }
277 }
278
279 @Override
280 public void synchronize() {
281 }
282
283 @Override
284 public void newRole(String role) {
285 try {
286 getUserManager().createGroup(role);
287 } catch (RepositoryException e) {
288 throw new ArgeoException("Cannot create role " + role, e);
289 }
290 }
291
292 @Override
293 public Set<String> listEditableRoles() {
294 LinkedHashSet<String> res = new LinkedHashSet<String>();
295 try {
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);
302 if (role != null
303 && !role.equals(KernelHeader.ROLE_GROUP_ADMIN)
304 && !(role.equals(KernelHeader.ROLE_ADMIN) && !SecurityUtils
305 .hasCurrentThreadAuthority(KernelHeader.ROLE_ADMIN)))
306 res.add(role);
307 }
308 return res;
309 } catch (RepositoryException e) {
310 throw new ArgeoException("Cannot list groups", e);
311 }
312 }
313
314 @Override
315 public void deleteRole(String role) {
316 try {
317 getUserManager().getAuthorizable(role).remove();
318 } catch (RepositoryException e) {
319 throw new ArgeoException("Cannot remove role " + role, e);
320 }
321 }
322
323 protected String roleToGroupName(String role) {
324 String groupName;
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;
329 else
330 groupName = role;
331 return groupName;
332 }
333
334 protected String groupNameToRole(String groupName) {
335 String role;
336 if (groupName.equals(UserAccessControlProvider.USER_ADMIN_GROUP_NAME)) {
337 role = KernelHeader.ROLE_USER_ADMIN;
338 } else if (groupName
339 .equals(UserAccessControlProvider.GROUP_ADMIN_GROUP_NAME)) {
340 role = KernelHeader.ROLE_GROUP_ADMIN;
341 } else if (groupName.equals(JACKR_ADMINISTRATORS)) {
342 return null;
343 } else {
344 role = groupName;
345 }
346 return role;
347 }
348
349 @Override
350 public UserDetails loadUserByUsername(String username)
351 throws UsernameNotFoundException, DataAccessException {
352 try {
353 User user = (User) getUserManager().getAuthorizable(username);
354 if (user == null)
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);
360 }
361 }
362
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);
368
369 ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
370 authorities.add(new GrantedAuthorityPrincipal(KernelHeader.ROLE_USER));
371
372 Group adminGroup = (Group) getUserManager().getAuthorizable(
373 KernelHeader.ROLE_ADMIN);
374
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);
380 } else {
381 groups = user.declaredMemberOf();
382 }
383
384 while (groups.hasNext()) {
385 Authorizable group = groups.next();
386 String groupName = group.getPrincipal().getName();
387 String role = groupNameToRole(groupName);
388 if (role != null)
389 authorities.add(new GrantedAuthorityPrincipal(role));
390 }
391
392 Node userProfile = UserJcrUtils.getUserProfile(session, username);
393 JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
394 authorities);
395 return userDetails;
396 }
397
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();
406 try {
407 SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
408 password);
409 User user = (User) getUserManager().getAuthorizable(username);
410 if (user == null)
411 throw new BadCredentialsException("Bad credentials");
412 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
413 .getCredentials();
414 // String providedPassword = siteAuth.getCredentials().toString();
415 if (!credentials.matches(sp))
416 throw new BadCredentialsException("Bad credentials");
417
418 // session = repository.login(sp, null);
419
420 Node userProfile = UserJcrUtils.getUserProfile(adminSession,
421 username);
422 JcrUserDetails.checkAccountStatus(userProfile);
423 } catch (BadCredentialsException e) {
424 throw e;
425 } catch (Exception e) {
426 throw new BadCredentialsException(
427 "Cannot authenticate " + siteAuth, e);
428 } finally {
429 Arrays.fill(password, '*');
430 }
431
432 try {
433 JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
434 username);
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);
442 }
443 }
444
445 @SuppressWarnings("rawtypes")
446 public boolean supports(Class authentication) {
447 return UsernamePasswordAuthenticationToken.class
448 .isAssignableFrom(authentication);
449 }
450
451 public void setRepository(Repository repository) {
452 this.repository = repository;
453 }
454
455 public void setSecurityModel(JcrSecurityModel securityModel) {
456 this.securityModel = securityModel;
457 }
458
459 }