Standalone authentication based on Jackrabbit.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Nov 2014 14:24:49 +0000 (14:24 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Nov 2014 14:24:49 +0000 (14:24 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@7543 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

org.argeo.security.dao.jackrabbit/META-INF/spring/remote.xml [new file with mode: 0644]
org.argeo.security.dao.jackrabbit/META-INF/spring/repofactory.xml [deleted file]
org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml
org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml
org.argeo.security.dao.jackrabbit/bnd.bnd
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitUserAdminService.java [new file with mode: 0644]

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 (file)
index 0000000..ff62a5c
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+       <!-- REMOTE -->
+
+       <!-- <bean id="repositoryFactory" class="org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory"> -->
+       <!-- <property name="bundleContext" ref="bundleContext" /> -->
+       <!-- </bean> -->
+
+       <!-- <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager"> -->
+       <!-- <property name="providers"> -->
+       <!-- <list> -->
+       <!-- <ref bean="authByAdapterProvider" /> -->
+       <!-- <ref bean="remoteJcrAuthenticationProvider" /> -->
+       <!-- </list> -->
+       <!-- </property> -->
+       <!-- </bean> -->
+
+       <!-- <bean id="remoteJcrAuthenticationProvider" class="org.argeo.security.jcr.RemoteJcrAuthenticationProvider"> -->
+       <!-- <property name="repositoryFactory" ref="repositoryFactory" /> -->
+       <!-- <property name="bundleContext" ref="bundleContext" /> -->
+       <!-- </bean> -->
+
+       <!-- <bean id="authByAdapterProvider" -->
+       <!-- class="org.springframework.security.adapters.AuthByAdapterProvider"> -->
+       <!-- <property name="key" value="${argeo.security.systemKey}" /> -->
+       <!-- </bean> -->
+
+       <!-- <bean id="userDetailsManager" class="org.argeo.security.jcr.OsJcrUserAdminService" -->
+       <!-- init-method="init" destroy-method="destroy"> -->
+       <!-- </bean> -->
+
+</beans>
\ 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 (file)
index a00c9b0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
-
-       <bean id="repositoryFactory" class="org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory">
-               <property name="bundleContext" ref="bundleContext" />
-       </bean>
-
-</beans>
\ No newline at end of file
index 9f6d4325691bd6916ca6400d5afe837068508c63..3d3f4010f7c9d5e0220284e78fe3a91ef43a99b5 100644 (file)
@@ -9,20 +9,19 @@
        http://www.springframework.org/schema/util\r
        http://www.springframework.org/schema/util/spring-util-2.5.xsd">\r
 \r
+       <!-- REFERENCE -->\r
+       <reference id="nodeRepository" interface="javax.jcr.Repository"\r
+               filter="(argeo.jcr.repository.alias=node)" />\r
+\r
        <!-- SERVICES -->\r
        <service ref="authenticationManager"\r
                interface="org.springframework.security.AuthenticationManager" />\r
 \r
-       <service ref="repositoryFactory" interface="javax.jcr.RepositoryFactory" />\r
-\r
        <!-- User management -->\r
        <service ref="userDetailsManager"\r
-               interface="org.springframework.security.userdetails.UserDetailsService"\r
-               context-class-loader="service-provider" />\r
+               interface="org.springframework.security.userdetails.UserDetailsService" />\r
        <service ref="userDetailsManager"\r
-               interface="org.springframework.security.userdetails.UserDetailsManager"\r
-               context-class-loader="service-provider" />\r
-       <service ref="userDetailsManager" interface="org.argeo.security.UserAdminService"\r
-               context-class-loader="service-provider" />\r
+               interface="org.springframework.security.userdetails.UserDetailsManager" />\r
+       <service ref="userDetailsManager" interface="org.argeo.security.UserAdminService" />\r
 \r
 </beans:beans>
\ No newline at end of file
index 1300a05506f8cc57c0f428994bd31714e8907a27..32baed4735e4c33989cb692f500361bfe5e06efc 100644 (file)
                <property name="providers">
                        <list>
                                <ref bean="authByAdapterProvider" />
-                               <ref bean="remoteJcrAuthenticationProvider" />
+                               <ref bean="userDetailsManager" />
                        </list>
                </property>
        </bean>
 
        <!-- Authentication providers -->
-       <bean id="remoteJcrAuthenticationProvider" class="org.argeo.security.jcr.RemoteJcrAuthenticationProvider">
-               <property name="repositoryFactory" ref="repositoryFactory" />
-               <property name="bundleContext" ref="bundleContext" />
-       </bean>
-
        <bean id="authByAdapterProvider"
                class="org.springframework.security.adapters.AuthByAdapterProvider">
                <property name="key" value="${argeo.security.systemKey}" />
        </bean>
 
        <!-- Dummy user manager -->
-       <bean id="userDetailsManager" class="org.argeo.security.jcr.OsJcrUserAdminService"
+       <bean id="userDetailsManager"
+               class="org.argeo.security.jackrabbit.JackrabbitUserAdminService"
+               init-method="init" destroy-method="destroy" depends-on="systemInit">
+               <property name="repository" ref="nodeRepository" />
+               <property name="securityModel" ref="jcrSecurityModel" />
+       </bean>
+
+       <bean name="jcrSecurityModel" class="org.argeo.security.jcr.SimpleJcrSecurityModel"
+               depends-on="argeoDataModel" />
+
+       <bean id="argeoDataModel" class="org.argeo.jackrabbit.JackrabbitWrapper"
                init-method="init" destroy-method="destroy">
+               <description><![CDATA[Make sure that Argeo base data model is registered]]></description>
+               <property name="cndFiles">
+                       <list>
+                               <value>/org/argeo/jcr/argeo.cnd</value>
+                       </list>
+               </property>
+               <property name="repository" ref="nodeRepository" />
+               <property name="bundleContext" ref="bundleContext" />
+       </bean>
+
+       <!-- Internal authentication, used by during the general authentication 
+               initialization himself, in order to prevent the following dependency cycle: 
+               Repository.login() <= AuthenticationManager <= JackrabbitUserAdminService 
+               <= Repository.login() in init() -->
+       <bean id="internalAuthenticationManager" class="org.springframework.security.providers.ProviderManager">
+               <property name="providers">
+                       <list>
+                               <ref bean="authByAdapterProvider" />
+                       </list>
+               </property>
+       </bean>
+
+       <bean id="systemInit"
+               class="org.argeo.security.core.AuthenticatedApplicationContextInitialization">
+               <description><![CDATA[Executes initialization with a system authentication]]></description>
+               <property name="authenticationManager" ref="internalAuthenticationManager" />
+               <property name="systemAuthenticationKey" value="${argeo.security.systemKey}" />
        </bean>
 
 </beans>
\ No newline at end of file
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0df3b4a6553d9510ee278294f1f78f5147d5d2c5 100644 (file)
@@ -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 (file)
index 0000000..79faeda
--- /dev/null
@@ -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<String> listUsers() {
+               LinkedHashSet<String> res = new LinkedHashSet<String>();
+               try {
+                       Iterator<Authorizable> 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<String> listUsersInRole(String role) {
+               LinkedHashSet<String> res = new LinkedHashSet<String>();
+               try {
+                       Group group = (Group) getUserManager().getAuthorizable(role);
+                       Iterator<Authorizable> 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<String> listEditableRoles() {
+               LinkedHashSet<String> res = new LinkedHashSet<String>();
+               try {
+                       Iterator<Authorizable> 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<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+               // FIXME make it more generic
+               authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
+               Iterator<Group> 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;
+       }
+
+}