package org.argeo.security.jackrabbit; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.jcr.Node; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import org.apache.jackrabbit.api.JackrabbitSession; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials; import org.argeo.ArgeoException; import org.argeo.jcr.JcrUtils; import org.argeo.jcr.UserJcrUtils; import org.argeo.security.UserAdminService; import org.argeo.security.jcr.JcrSecurityModel; import org.argeo.security.jcr.JcrUserDetails; import org.springframework.dao.DataAccessException; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * An implementation of {@link UserAdminService} which closely wraps Jackrabbits * implementation. Roles are implemented with Groups. */ public class JackrabbitUserAdminService implements UserAdminService, AuthenticationProvider { final static String userRole = "ROLE_USER"; final static String adminRole = "ROLE_ADMIN"; private Repository repository; private JcrSecurityModel securityModel; private JackrabbitSession adminSession = null; private String superUsername = "root"; private String superUserInitialPassword = "demo"; public void init() throws RepositoryException { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); authentication.getName(); adminSession = (JackrabbitSession) repository.login(); Authorizable adminGroup = getUserManager().getAuthorizable(adminRole); if (adminGroup == null) { adminGroup = getUserManager().createGroup(adminRole); adminSession.save(); } Authorizable superUser = getUserManager() .getAuthorizable(superUsername); if (superUser == null) { superUser = getUserManager().createUser(superUsername, superUserInitialPassword); ((Group) adminGroup).addMember(superUser); securityModel.sync(adminSession, superUsername, null); adminSession.save(); } } public void destroy() throws RepositoryException { JcrUtils.logoutQuietly(adminSession); } private UserManager getUserManager() throws RepositoryException { return adminSession.getUserManager(); } @Override public void createUser(UserDetails user) { try { // FIXME workaround for issue in new user wizard where // security model is hardcoded and it already exists if (getUserManager().getAuthorizable(user.getUsername()) == null) { getUserManager().createUser(user.getUsername(), user.getPassword()); securityModel.sync(adminSession, user.getUsername(), null); } updateUser(user); } catch (RepositoryException e) { throw new ArgeoException("Cannot create user " + user, e); } } @Override public void updateUser(UserDetails userDetails) { try { User user = (User) getUserManager().getAuthorizable( userDetails.getUsername()); // new password char[] newPassword = userDetails.getPassword().toCharArray(); SimpleCredentials sp = new SimpleCredentials( userDetails.getUsername(), newPassword); CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user .getCredentials(); if (!credentials.matches(sp)) user.changePassword(new String(newPassword)); List roles = new ArrayList(); for (GrantedAuthority ga : userDetails.getAuthorities()) { if (ga.getAuthority().equals(userRole)) continue; roles.add(ga.getAuthority()); } for (Iterator it = user.memberOf(); it.hasNext();) { Group group = it.next(); if (roles.contains(group.getPrincipal().getName())) roles.remove(group.getPrincipal().getName()); else group.removeMember(user); } // remaining (new ones) for (String role : roles) { Group group = (Group) getUserManager().getAuthorizable(role); if (group == null) throw new ArgeoException("Group " + role + " does not exist," + " whereas it was granted to user " + userDetails); group.addMember(user); } } catch (Exception e) { throw new ArgeoException("Cannot update user details", e); } } @Override public void deleteUser(String username) { try { getUserManager().getAuthorizable(username).remove(); } catch (RepositoryException e) { throw new ArgeoException("Cannot remove user " + username, e); } } @Override public void changePassword(String oldPassword, String newPassword) { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); try { SimpleCredentials sp = new SimpleCredentials( authentication.getName(), ((UserDetails) authentication.getDetails()).getPassword() .toCharArray()); User user = (User) getUserManager().getAuthorizable( authentication.getName()); CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user .getCredentials(); if (credentials.matches(sp)) user.changePassword(newPassword); else throw new BadCredentialsException("Bad credentials provided"); } catch (Exception e) { throw new ArgeoException("Cannot change password for user " + authentication.getName(), e); } } @Override public boolean userExists(String username) { try { Authorizable authorizable = getUserManager().getAuthorizable( username); if (authorizable != null && authorizable instanceof User) return true; return false; } catch (RepositoryException e) { throw new ArgeoException("Cannot check whether user " + username + " exists ", e); } } @Override public Set listUsers() { LinkedHashSet res = new LinkedHashSet(); try { Iterator users = getUserManager().findAuthorizables( "rep:principalName", null, UserManager.SEARCH_TYPE_USER); while (users.hasNext()) { res.add(users.next().getPrincipal().getName()); } return res; } catch (RepositoryException e) { throw new ArgeoException("Cannot list users", e); } } @Override public Set listUsersInRole(String role) { LinkedHashSet res = new LinkedHashSet(); try { Group group = (Group) getUserManager().getAuthorizable(role); Iterator users = group.getMembers(); // NB: not recursive while (users.hasNext()) { res.add(users.next().getPrincipal().getName()); } return res; } catch (RepositoryException e) { throw new ArgeoException("Cannot list users in role " + role, e); } } @Override public void synchronize() { } @Override public void newRole(String role) { try { getUserManager().createGroup(role); } catch (RepositoryException e) { throw new ArgeoException("Cannot create role " + role, e); } } @Override public Set listEditableRoles() { LinkedHashSet res = new LinkedHashSet(); try { Iterator groups = getUserManager().findAuthorizables( "rep:principalName", null, UserManager.SEARCH_TYPE_GROUP); while (groups.hasNext()) { res.add(groups.next().getPrincipal().getName()); } return res; } catch (RepositoryException e) { throw new ArgeoException("Cannot list groups", e); } } @Override public void deleteRole(String role) { try { getUserManager().getAuthorizable(role).remove(); } catch (RepositoryException e) { throw new ArgeoException("Cannot remove role " + role, e); } } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { try { User user = (User) getUserManager().getAuthorizable(username); if (user == null) throw new UsernameNotFoundException("User " + username + " cannot be found"); return loadJcrUserDetails(adminSession, username, user.getCredentials()); } catch (RepositoryException e) { throw new ArgeoException("Cannot load user " + username, e); } } protected JcrUserDetails loadJcrUserDetails(Session session, String username, Object credentials) throws RepositoryException { if (username == null) username = session.getUserID(); User user = (User) getUserManager().getAuthorizable(username); ArrayList authorities = new ArrayList(); // FIXME make it more generic authorities.add(new SimpleGrantedAuthority("ROLE_USER")); Iterator groups = user.declaredMemberOf(); while (groups.hasNext()) { Group group = groups.next(); // String role = "ROLE_" // + group.getPrincipal().getName().toUpperCase(); String role = group.getPrincipal().getName(); authorities.add(new SimpleGrantedAuthority(role)); } Node userProfile = UserJcrUtils.getUserProfile(session, username); JcrUserDetails userDetails = new JcrUserDetails(userProfile, credentials.toString(), authorities); return userDetails; } // AUTHENTICATION PROVIDER public synchronized Authentication authenticate( Authentication authentication) throws AuthenticationException { UsernamePasswordAuthenticationToken siteAuth = (UsernamePasswordAuthenticationToken) authentication; String username = siteAuth.getName(); try { SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), siteAuth.getCredentials().toString().toCharArray()); User user = (User) getUserManager().getAuthorizable(username); CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user .getCredentials(); // String providedPassword = siteAuth.getCredentials().toString(); if (!credentials.matches(sp)) { throw new BadCredentialsException("Passwords do not match"); } // session = repository.login(sp, null); Node userProfile = UserJcrUtils.getUserProfile(adminSession, username); JcrUserDetails.checkAccountStatus(userProfile); } catch (Exception e) { throw new BadCredentialsException( "Cannot authenticate " + siteAuth, e); } try { JcrUserDetails userDetails = loadJcrUserDetails(adminSession, username, siteAuth.getCredentials()); UsernamePasswordAuthenticationToken authenticated = new UsernamePasswordAuthenticationToken( siteAuth, "", userDetails.getAuthorities()); authenticated.setDetails(userDetails); return authenticated; } catch (RepositoryException e) { throw new ArgeoException( "Unexpected exception when authenticating " + siteAuth, e); } } @SuppressWarnings("rawtypes") public boolean supports(Class authentication) { return UsernamePasswordAuthenticationToken.class .isAssignableFrom(authentication); } public void setRepository(Repository repository) { this.repository = repository; } public void setSecurityModel(JcrSecurityModel securityModel) { this.securityModel = securityModel; } }