From ae83aa02fe3c5b3308d4d9857a7cf618b2e4d81e Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 26 Nov 2014 14:24:49 +0000 Subject: [PATCH] Standalone authentication based on Jackrabbit. git-svn-id: https://svn.argeo.org/commons/trunk@7543 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../META-INF/spring/remote.xml | 35 ++ .../META-INF/spring/repofactory.xml | 10 - .../META-INF/spring/security-jcr-osgi.xml | 15 +- .../META-INF/spring/security-jcr-services.xml | 46 ++- org.argeo.security.dao.jackrabbit/bnd.bnd | 1 + .../JackrabbitUserAdminService.java | 298 ++++++++++++++++++ 6 files changed, 380 insertions(+), 25 deletions(-) create mode 100644 org.argeo.security.dao.jackrabbit/META-INF/spring/remote.xml delete mode 100644 org.argeo.security.dao.jackrabbit/META-INF/spring/repofactory.xml create mode 100644 org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitUserAdminService.java diff --git a/org.argeo.security.dao.jackrabbit/META-INF/spring/remote.xml b/org.argeo.security.dao.jackrabbit/META-INF/spring/remote.xml new file mode 100644 index 000000000..ff62a5c64 --- /dev/null +++ b/org.argeo.security.dao.jackrabbit/META-INF/spring/remote.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.security.dao.jackrabbit/META-INF/spring/repofactory.xml b/org.argeo.security.dao.jackrabbit/META-INF/spring/repofactory.xml deleted file mode 100644 index a00c9b00d..000000000 --- a/org.argeo.security.dao.jackrabbit/META-INF/spring/repofactory.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml b/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml index 9f6d43256..3d3f4010f 100644 --- a/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml +++ b/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml @@ -9,20 +9,19 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> + + + - - + interface="org.springframework.security.userdetails.UserDetailsService" /> - + interface="org.springframework.security.userdetails.UserDetailsManager" /> + \ No newline at end of file diff --git a/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml b/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml index 1300a0550..32baed473 100644 --- a/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml +++ b/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml @@ -16,25 +16,57 @@ - + - - - - - - + + + + + + + + + + + /org/argeo/jcr/argeo.cnd + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.security.dao.jackrabbit/bnd.bnd b/org.argeo.security.dao.jackrabbit/bnd.bnd index e69de29bb..0df3b4a65 100644 --- a/org.argeo.security.dao.jackrabbit/bnd.bnd +++ b/org.argeo.security.dao.jackrabbit/bnd.bnd @@ -0,0 +1 @@ +Import-Package: org.argeo.jcr,* \ No newline at end of file diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitUserAdminService.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitUserAdminService.java new file mode 100644 index 000000000..79faeda5c --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitUserAdminService.java @@ -0,0 +1,298 @@ +package org.argeo.security.jackrabbit; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +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; +import org.springframework.security.AuthenticationException; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.AuthenticationProvider; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.UsernameNotFoundException; + +/** + * An implementation of {@link UserAdminService} which closely wraps Jackrabbits + * implementation. Roles are implemented with Groups. + */ +public class JackrabbitUserAdminService implements UserAdminService, + AuthenticationProvider { + 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("ROLE_ADMIN"); + if (adminGroup == null) { + adminGroup = getUserManager().createGroup("ROLE_ADMIN"); + 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 { + getUserManager().createUser(user.getUsername(), user.getPassword()); + securityModel.sync(adminSession, user.getUsername(), null); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot create user " + user, e); + } + } + + @Override + public void updateUser(UserDetails user) { + + } + + @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(), authentication.getCredentials() + .toString().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( + null, 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( + null, 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); + 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 GrantedAuthorityImpl("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 GrantedAuthorityImpl(role)); + } + + Node userProfile = UserJcrUtils.getUserProfile(session, username); + JcrUserDetails userDetails = new JcrUserDetails(userProfile, + credentials.toString(), + authorities.toArray(new GrantedAuthority[authorities.size()])); + 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; + } + +} -- 2.30.2