]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitUserAdminService.java
Start improving CMS login
[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.version.VersionManager;
16
17 import org.apache.jackrabbit.api.JackrabbitSession;
18 import org.apache.jackrabbit.api.security.user.Authorizable;
19 import org.apache.jackrabbit.api.security.user.Group;
20 import org.apache.jackrabbit.api.security.user.User;
21 import org.apache.jackrabbit.api.security.user.UserManager;
22 import org.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials;
23 import org.apache.jackrabbit.core.security.user.UserAccessControlProvider;
24 import org.argeo.ArgeoException;
25 import org.argeo.cms.KernelHeader;
26 import org.argeo.cms.internal.auth.GrantedAuthorityPrincipal;
27 import org.argeo.cms.internal.auth.JcrSecurityModel;
28 import org.argeo.jcr.JcrUtils;
29 import org.argeo.jcr.UserJcrUtils;
30 import org.argeo.security.NodeAuthenticationToken;
31 import org.argeo.security.SecurityUtils;
32 import org.argeo.security.UserAdminService;
33 import org.argeo.security.jcr.JcrUserDetails;
34 import org.argeo.security.jcr.NewUserDetails;
35 import org.springframework.dao.DataAccessException;
36 import org.springframework.security.authentication.AuthenticationProvider;
37 import org.springframework.security.authentication.BadCredentialsException;
38 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
39 import org.springframework.security.core.Authentication;
40 import org.springframework.security.core.AuthenticationException;
41 import org.springframework.security.core.GrantedAuthority;
42 import org.springframework.security.core.context.SecurityContextHolder;
43 import org.springframework.security.core.userdetails.UserDetails;
44 import org.springframework.security.core.userdetails.UsernameNotFoundException;
45
46 /**
47 * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
48 * implementation. Roles are implemented with Groups.
49 */
50 public class JackrabbitUserAdminService implements UserAdminService,
51 AuthenticationProvider {
52 private final static String JACKR_ADMINISTRATORS = "administrators";
53 private final static String REP_PRINCIPAL_NAME = "rep:principalName";
54
55 private Repository repository;
56 private JcrSecurityModel securityModel;
57
58 private JackrabbitSession adminSession = null;
59
60 private String superUserInitialPassword = "demo";
61
62 public void init() throws RepositoryException {
63 Authentication authentication = SecurityContextHolder.getContext()
64 .getAuthentication();
65 authentication.getName();
66 adminSession = (JackrabbitSession) repository.login();
67 Authorizable adminGroup = getUserManager().getAuthorizable(
68 KernelHeader.ROLE_ADMIN);
69 if (adminGroup == null) {
70 adminGroup = getUserManager().createGroup(KernelHeader.ROLE_ADMIN);
71 adminSession.save();
72 }
73 Authorizable superUser = getUserManager().getAuthorizable(
74 KernelHeader.USERNAME_ADMIN);
75 if (superUser == null) {
76 superUser = getUserManager().createUser(
77 KernelHeader.USERNAME_ADMIN, superUserInitialPassword);
78 ((Group) adminGroup).addMember(superUser);
79 securityModel.sync(adminSession, KernelHeader.USERNAME_ADMIN, null);
80 adminSession.save();
81 }
82 securityModel.init(adminSession);
83 }
84
85 public void destroy() throws RepositoryException {
86 JcrUtils.logoutQuietly(adminSession);
87 }
88
89 private UserManager getUserManager() throws RepositoryException {
90 return adminSession.getUserManager();
91 }
92
93 @Override
94 public void createUser(UserDetails user) {
95 try {
96 // if (getUserManager().getAuthorizable(user.getUsername()) == null)
97 // {
98 getUserManager().createUser(user.getUsername(), user.getPassword());
99 Node userProfile = securityModel.sync(adminSession,
100 user.getUsername(), null);
101 if (user instanceof NewUserDetails)
102 ((NewUserDetails) user).mapToProfileNode(userProfile);
103 userProfile.getSession().save();
104
105 // check in node
106 VersionManager versionManager = userProfile.getSession()
107 .getWorkspace().getVersionManager();
108 if (versionManager.isCheckedOut(userProfile.getPath()))
109 versionManager.checkin(userProfile.getPath());
110 // }
111 updateUser(user);
112 } catch (RepositoryException e) {
113 throw new ArgeoException("Cannot create user " + user, e);
114 }
115 }
116
117 @Override
118 public void updateUser(UserDetails userDetails) {
119 try {
120 User user = (User) getUserManager().getAuthorizable(
121 userDetails.getUsername());
122 if (user == null)
123 throw new ArgeoException("No user " + userDetails.getUsername());
124
125 // new password
126 String newPassword = userDetails.getPassword();
127 if (!newPassword.trim().equals("")) {
128 SimpleCredentials sp = new SimpleCredentials(
129 userDetails.getUsername(), newPassword.toCharArray());
130 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
131 .getCredentials();
132 if (!credentials.matches(sp))
133 user.changePassword(new String(newPassword));
134 }
135
136 List<String> roles = new ArrayList<String>();
137 for (GrantedAuthority ga : userDetails.getAuthorities()) {
138 if (ga.getAuthority().equals(KernelHeader.ROLE_USER))
139 continue;
140 roles.add(ga.getAuthority());
141 }
142
143 groups: for (Iterator<Group> it = user.memberOf(); it.hasNext();) {
144 Group group = it.next();
145 String groupName = group.getPrincipal().getName();
146 String role = groupNameToRole(groupName);
147 if (role == null)
148 continue groups;
149
150 if (roles.contains(role))
151 roles.remove(role);
152 else {
153 group.removeMember(user);
154 if (role.equals(KernelHeader.ROLE_ADMIN)) {
155 Group administratorsGroup = ((Group) getUserManager()
156 .getAuthorizable(JACKR_ADMINISTRATORS));
157 if (administratorsGroup.isDeclaredMember(user))
158 administratorsGroup.removeMember(user);
159 }
160 }
161 }
162
163 // remaining (new memberships)
164 for (String role : roles) {
165 String groupName = roleToGroupName(role);
166 Group group = (Group) getUserManager().getAuthorizable(
167 groupName);
168 if (group == null)
169 throw new ArgeoException("Group " + role
170 + " does not exist,"
171 + " whereas it was granted to user " + userDetails);
172 group.addMember(user);
173
174 // add to Jackrabbit administrators
175 if (role.equals(KernelHeader.ROLE_ADMIN)) {
176 Group administratorsGroup = (Group) getUserManager()
177 .getAuthorizable(JACKR_ADMINISTRATORS);
178 administratorsGroup.addMember(user);
179 }
180
181 }
182 } catch (Exception e) {
183 throw new ArgeoException("Cannot update user details", e);
184 }
185
186 }
187
188 @Override
189 public void deleteUser(String username) {
190 try {
191 getUserManager().getAuthorizable(username).remove();
192 } catch (RepositoryException e) {
193 throw new ArgeoException("Cannot remove user " + username, e);
194 }
195 }
196
197 @Override
198 public void changePassword(String oldPassword, String newPassword) {
199 Authentication authentication = SecurityContextHolder.getContext()
200 .getAuthentication();
201 String username = authentication.getName();
202 try {
203 SimpleCredentials sp = new SimpleCredentials(username,
204 oldPassword.toCharArray());
205 User user = (User) getUserManager().getAuthorizable(username);
206 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
207 .getCredentials();
208 if (credentials.matches(sp))
209 user.changePassword(newPassword);
210 else
211 throw new BadCredentialsException("Bad credentials provided");
212 } catch (Exception e) {
213 throw new ArgeoException("Cannot change password for user "
214 + username, e);
215 }
216 }
217
218 @Override
219 public boolean userExists(String username) {
220 try {
221 Authorizable authorizable = getUserManager().getAuthorizable(
222 username);
223 if (authorizable != null && authorizable instanceof User)
224 return true;
225 return false;
226 } catch (RepositoryException e) {
227 throw new ArgeoException("Cannot check whether user " + username
228 + " exists ", e);
229 }
230 }
231
232 @Override
233 public Set<String> listUsers() {
234 LinkedHashSet<String> res = new LinkedHashSet<String>();
235 try {
236 Iterator<Authorizable> users = getUserManager().findAuthorizables(
237 "rep:principalName", null, UserManager.SEARCH_TYPE_USER);
238 while (users.hasNext()) {
239 res.add(users.next().getPrincipal().getName());
240 }
241 return res;
242 } catch (RepositoryException e) {
243 throw new ArgeoException("Cannot list users", e);
244 }
245 }
246
247 @Override
248 public Set<String> listUsersInRole(String role) {
249 LinkedHashSet<String> res = new LinkedHashSet<String>();
250 try {
251 Group group = (Group) getUserManager().getAuthorizable(role);
252 Iterator<Authorizable> users = group.getMembers();
253 // NB: not recursive
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 in role " + role, e);
260 }
261 }
262
263 @Override
264 public void synchronize() {
265 }
266
267 @Override
268 public void newRole(String role) {
269 try {
270 getUserManager().createGroup(role);
271 } catch (RepositoryException e) {
272 throw new ArgeoException("Cannot create role " + role, e);
273 }
274 }
275
276 @Override
277 public Set<String> listEditableRoles() {
278 LinkedHashSet<String> res = new LinkedHashSet<String>();
279 try {
280 Iterator<Authorizable> groups = getUserManager().findAuthorizables(
281 REP_PRINCIPAL_NAME, null, UserManager.SEARCH_TYPE_GROUP);
282 while (groups.hasNext()) {
283 Group group = (Group) groups.next();
284 String groupName = group.getPrincipal().getName();
285 String role = groupNameToRole(groupName);
286 if (role != null
287 && !role.equals(KernelHeader.ROLE_GROUP_ADMIN)
288 && !(role.equals(KernelHeader.ROLE_ADMIN) && !SecurityUtils
289 .hasCurrentThreadAuthority(KernelHeader.ROLE_ADMIN)))
290 res.add(role);
291 }
292 return res;
293 } catch (RepositoryException e) {
294 throw new ArgeoException("Cannot list groups", e);
295 }
296 }
297
298 @Override
299 public void deleteRole(String role) {
300 try {
301 getUserManager().getAuthorizable(role).remove();
302 } catch (RepositoryException e) {
303 throw new ArgeoException("Cannot remove role " + role, e);
304 }
305 }
306
307 protected String roleToGroupName(String role) {
308 String groupName;
309 if (role.equals(KernelHeader.ROLE_USER_ADMIN))
310 groupName = UserAccessControlProvider.USER_ADMIN_GROUP_NAME;
311 else if (role.equals(KernelHeader.ROLE_GROUP_ADMIN))
312 groupName = UserAccessControlProvider.GROUP_ADMIN_GROUP_NAME;
313 else
314 groupName = role;
315 return groupName;
316 }
317
318 protected String groupNameToRole(String groupName) {
319 String role;
320 if (groupName.equals(UserAccessControlProvider.USER_ADMIN_GROUP_NAME)) {
321 role = KernelHeader.ROLE_USER_ADMIN;
322 } else if (groupName
323 .equals(UserAccessControlProvider.GROUP_ADMIN_GROUP_NAME)) {
324 role = KernelHeader.ROLE_GROUP_ADMIN;
325 } else if (groupName.equals(JACKR_ADMINISTRATORS)) {
326 return null;
327 } else {
328 role = groupName;
329 }
330 return role;
331 }
332
333 @Override
334 public UserDetails loadUserByUsername(String username)
335 throws UsernameNotFoundException, DataAccessException {
336 try {
337 User user = (User) getUserManager().getAuthorizable(username);
338 if (user == null)
339 throw new UsernameNotFoundException("User " + username
340 + " cannot be found");
341 return loadJcrUserDetails(adminSession, username);
342 } catch (RepositoryException e) {
343 throw new ArgeoException("Cannot load user " + username, e);
344 }
345 }
346
347 protected JcrUserDetails loadJcrUserDetails(Session session, String username)
348 throws RepositoryException {
349 if (username == null)
350 username = session.getUserID();
351 User user = (User) getUserManager().getAuthorizable(username);
352
353 ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
354 authorities.add(new GrantedAuthorityPrincipal(KernelHeader.ROLE_USER));
355
356 Group adminGroup = (Group) getUserManager().getAuthorizable(
357 KernelHeader.ROLE_ADMIN);
358
359 Iterator<? extends Authorizable> groups;
360 if (username.equals(KernelHeader.USERNAME_ADMIN)
361 || adminGroup.isDeclaredMember(user)) {
362 groups = getUserManager().findAuthorizables(REP_PRINCIPAL_NAME,
363 null, UserManager.SEARCH_TYPE_GROUP);
364 } else {
365 groups = user.declaredMemberOf();
366 }
367
368 while (groups.hasNext()) {
369 Authorizable group = groups.next();
370 String groupName = group.getPrincipal().getName();
371 String role = groupNameToRole(groupName);
372 if (role != null)
373 authorities.add(new GrantedAuthorityPrincipal(role));
374 }
375
376 Node userProfile = UserJcrUtils.getUserProfile(session, username);
377 JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
378 authorities);
379 return userDetails;
380 }
381
382 // AUTHENTICATION PROVIDER
383 public synchronized Authentication authenticate(
384 Authentication authentication) throws AuthenticationException {
385 NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
386 String username = siteAuth.getName();
387 if (!(siteAuth.getCredentials() instanceof char[]))
388 throw new ArgeoException("Only char array passwords are supported");
389 char[] password = (char[]) siteAuth.getCredentials();
390 try {
391 SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
392 password);
393 User user = (User) getUserManager().getAuthorizable(username);
394 if (user == null)
395 throw new BadCredentialsException("Bad credentials");
396 CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
397 .getCredentials();
398 // String providedPassword = siteAuth.getCredentials().toString();
399 if (!credentials.matches(sp))
400 throw new BadCredentialsException("Bad credentials");
401
402 // session = repository.login(sp, null);
403
404 Node userProfile = UserJcrUtils.getUserProfile(adminSession,
405 username);
406 JcrUserDetails.checkAccountStatus(userProfile);
407 } catch (BadCredentialsException e) {
408 throw e;
409 } catch (Exception e) {
410 throw new BadCredentialsException(
411 "Cannot authenticate " + siteAuth, e);
412 } finally {
413 Arrays.fill(password, '*');
414 }
415
416 try {
417 JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
418 username);
419 NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
420 siteAuth, userDetails.getAuthorities());
421 authenticated.setDetails(userDetails);
422 return authenticated;
423 } catch (RepositoryException e) {
424 throw new ArgeoException(
425 "Unexpected exception when authenticating " + siteAuth, e);
426 }
427 }
428
429 @SuppressWarnings("rawtypes")
430 public boolean supports(Class authentication) {
431 return UsernamePasswordAuthenticationToken.class
432 .isAssignableFrom(authentication);
433 }
434
435 public void setRepository(Repository repository) {
436 this.repository = repository;
437 }
438
439 public void setSecurityModel(JcrSecurityModel securityModel) {
440 this.securityModel = securityModel;
441 }
442
443 }