Browse Source

Make User available from Authentication via DaoAuthenticationProvider.

Ben Alex 21 years ago
parent
commit
1a0bec5bf1

+ 7 - 0
changelog.txt

@@ -1,3 +1,10 @@
+Changes in version 0.6 (2004-xx-xx)
+-----------------------------------
+
+* Added feature so DaoAuthenticationProvider returns User in Authentication
+* Fixed Linux compatibility issues (directory case sensitivity etc)
+* Documentation improvements
+
 Changes in version 0.51 (2004-06-06)
 ------------------------------------
 

+ 48 - 14
core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java

@@ -51,12 +51,26 @@ import org.springframework.dao.DataAccessException;
  * <p>
  * Upon successful validation, a
  * <code>UsernamePasswordAuthenticationToken</code> will be created and
- * returned to the caller. In addition, the {@link User} will be placed in the
- * {@link UserCache} so that subsequent requests with the same username can be
- * validated without needing to query the {@link AuthenticationDao}. It should
- * be noted that if a user appears to present an incorrect password, the
- * {@link AuthenticationDao} will be queried to confirm the most up-to-date
- * password was used for comparison.
+ * returned to the caller. The token will include as its principal either a
+ * <code>String</code> representation of the username, or the {@link User}
+ * that was returned from the authentication repository. Using
+ * <code>String</code> is appropriate if a container adapter is being used, as
+ * it expects <code>String</code> representations of the username. Using
+ * <code>User</code> is appropriate if you require access to additional
+ * properties of the authenticated user, such as email addresses,
+ * human-friendly names etc. As container adapters are not recommended to be
+ * used, and <code>User</code> provides additional flexibility, by default a
+ * <code>User</code> is returned. To override this default, set the {@link
+ * #setForcePrincipalAsString} to <code>true</code>.
+ * </p>
+ * 
+ * <P>
+ * Caching is handled via the <code>User</code> object being placed in the
+ * {@link UserCache}. This ensures that subsequent requests with the same
+ * username can be validated without needing to query the {@link
+ * AuthenticationDao}. It should be noted that if a user appears to present an
+ * incorrect password, the {@link AuthenticationDao} will be queried to
+ * confirm the most up-to-date password was used for comparison.
  * </p>
  * 
  * <P>
@@ -79,6 +93,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
     private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
     private SaltSource saltSource;
     private UserCache userCache = new NullUserCache();
+    private boolean forcePrincipalAsString = false;
 
     //~ Methods ================================================================
 
@@ -95,6 +110,14 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
         return authenticationDao;
     }
 
+    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
+        this.forcePrincipalAsString = forcePrincipalAsString;
+    }
+
+    public boolean isForcePrincipalAsString() {
+        return forcePrincipalAsString;
+    }
+
     /**
      * Sets the PasswordEncoder instance to be used to encode and validate
      * passwords. If not set, {@link PlaintextPasswordEncoder} will be used by
@@ -148,13 +171,19 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
 
     public Authentication authenticate(Authentication authentication)
         throws AuthenticationException {
+        // Determine username
+        String username = authentication.getPrincipal().toString();
+
+        if (authentication.getPrincipal() instanceof User) {
+            username = ((User) authentication.getPrincipal()).getUsername();
+        }
+
         boolean cacheWasUsed = true;
-        User user = this.userCache.getUserFromCache(authentication.getPrincipal()
-                                                                  .toString());
+        User user = this.userCache.getUserFromCache(username);
 
         if (user == null) {
             cacheWasUsed = false;
-            user = getUserFromBackend(authentication);
+            user = getUserFromBackend(username);
         }
 
         if (!user.isEnabled()) {
@@ -170,7 +199,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             // Password incorrect, so ensure we're using most current password
             if (cacheWasUsed) {
                 cacheWasUsed = false;
-                user = getUserFromBackend(authentication);
+                user = getUserFromBackend(username);
             }
 
             if (!isPasswordCorrect(authentication, user)) {
@@ -194,9 +223,15 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             }
         }
 
+        Object principalToReturn = user;
+
+        if (forcePrincipalAsString) {
+            principalToReturn = user.getUsername();
+        }
+
         // Ensure we return the original credentials the user supplied,
         // so subsequent attempts are successful even with encoded passwords
-        return new UsernamePasswordAuthenticationToken(user.getUsername(),
+        return new UsernamePasswordAuthenticationToken(principalToReturn,
             authentication.getCredentials(), user.getAuthorities());
     }
 
@@ -220,10 +255,9 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             authentication.getCredentials().toString(), salt);
     }
 
-    private User getUserFromBackend(Authentication authentication) {
+    private User getUserFromBackend(String username) {
         try {
-            return this.authenticationDao.loadUserByUsername(authentication.getPrincipal()
-                                                                           .toString());
+            return this.authenticationDao.loadUserByUsername(username);
         } catch (UsernameNotFoundException notFound) {
             throw new BadCredentialsException("Bad credentials presented");
         } catch (DataAccessException repositoryProblem) {

+ 1 - 0
core/src/main/resources/org/acegisecurity/adapters/acegisecurity.xml

@@ -36,6 +36,7 @@
 	<!-- Authentication provider that queries our data access object  -->
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
+     	<property name="forcePrincipalAsString"><value>true</value></property>
 	</bean>
 
 	<!-- The authentication manager that iterates through our only authentication provider -->

+ 1 - 0
core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml

@@ -36,6 +36,7 @@
 	<!-- Authentication provider that queries our data access object  -->
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
+     	<property name="forcePrincipalAsString"><value>true</value></property>
 	</bean>
 
 	<!-- The authentication manager that iterates through our only authentication provider -->

+ 2 - 2
core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java

@@ -166,7 +166,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
         }
 
         UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
-        assertEquals("marissa", castResult.getPrincipal());
+        assertEquals(User.class, castResult.getPrincipal().getClass());
         assertEquals("koala", castResult.getCredentials());
         assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
         assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
@@ -192,7 +192,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
         }
 
         UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
-        assertEquals("marissa", castResult.getPrincipal());
+        assertEquals(User.class, castResult.getPrincipal().getClass());
 
         // We expect original credentials user submitted to be returned
         assertEquals("koala", castResult.getCredentials());

+ 5 - 4
core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java

@@ -24,6 +24,7 @@ import net.sf.acegisecurity.MockFilterConfig;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletResponse;
 import net.sf.acegisecurity.MockHttpSession;
+import net.sf.acegisecurity.providers.dao.User;
 import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
 
 import org.apache.commons.codec.binary.Base64;
@@ -199,8 +200,8 @@ public class BasicProcessingFilterTests extends TestCase {
 
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
         assertEquals("marissa",
-            ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal()
-             .toString());
+            ((User) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal())
+             .getUsername());
     }
 
     public void testOtherAuthorizationSchemeIsIgnored()
@@ -291,8 +292,8 @@ public class BasicProcessingFilterTests extends TestCase {
 
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
         assertEquals("marissa",
-            ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal()
-             .toString());
+                ((User) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal())
+                 .getUsername());
 
         // NOW PERFORM FAILED AUTHENTICATION
         // Setup our HTTP request

+ 7 - 2
core/src/test/java/org/acegisecurity/ui/cas/CasProcessingFilterEntryPointTests.java

@@ -20,6 +20,8 @@ import junit.framework.TestCase;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletResponse;
 
+import java.net.URLEncoder;
+
 
 /**
  * Tests {@link CasProcessingFilterEntryPoint}.
@@ -99,8 +101,11 @@ public class CasProcessingFilterEntryPointTests extends TestCase {
 
         ep.afterPropertiesSet();
         ep.commence(request, response);
-        assertEquals("https://cas/login?service=https://mycompany.com/bigWebApp/j_acegi_cas_security_check",
-            response.getRedirect());
+
+        assertEquals("https://cas/login?service="
+            + URLEncoder.encode(
+                "https://mycompany.com/bigWebApp/j_acegi_cas_security_check",
+                "UTF-8"), response.getRedirect());
     }
 
     public void testNormalOperationWithRenewTrue() throws Exception {

+ 25 - 6
docs/reference/src/index.xml

@@ -7,7 +7,7 @@
 
     <subtitle>Reference Documentation</subtitle>
 
-    <releaseinfo>0.51</releaseinfo>
+    <releaseinfo>0.6</releaseinfo>
 
     <authorgroup>
       <author>
@@ -946,10 +946,24 @@
         increased the complexity of the <literal>AuthenticationDao</literal>
         interface. For instance, a method would be required to increase the
         count of unsuccessful authentication attempts. Such functionality
-        could be easily provided in a new
-        <literal>AuthenticationManager</literal> or
-        <literal>AuthenticationProvider</literal> implementation if it were
-        desired.</para>
+        could be easily provided by leveraging the application event
+        publishing features discussed below.</para>
+
+        <para><literal>DaoAuthenticationProvider</literal> returns an
+        <literal>Authentication</literal> object which in turn has its
+        <literal>principal</literal> property set. The principal will be
+        either a <literal>String</literal> (which is essentially the username)
+        or a <literal>User</literal> object (which was looked up from the
+        <literal>AuthenticationDao</literal>). By default the
+        <literal>User</literal> is returned, as this enables applications to
+        subclass <literal>User</literal> and add extra properties potentially
+        of use in applications, such as the user's full name, email address
+        etc. If using container adapters, or if your applications were written
+        to operate with <literal>String</literal>s (as was the case for
+        releases prior to Acegi Security 0.6), you should set the
+        <literal>DaoAuthenticationProvider.forcePrincipalAsString</literal>
+        property to <literal>true</literal> in your application
+        context.</para>
       </sect2>
 
       <sect2 id="security-authentication-provider-events">
@@ -1927,6 +1941,11 @@ public boolean supports(Class clazz);</programlisting></para>
         provided below. Once installed, please take the time to try the sample
         application to ensure your container adapter is properly
         configured.</para>
+
+        <para>When using container adapters with the
+        <literal>DaoAuthenticationProvider</literal>, ensure you set its
+        <literal>forcePrincipalAsString</literal> property to
+        <literal>true</literal>.</para>
       </sect2>
 
       <sect2 id="security-container-adapters-catalina">
@@ -2497,7 +2516,7 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
         <literal>PasswordHandler</literal> will do).</para>
 
         <para>To install, you will need to download and extract the CAS server
-        archive. We used version 2.0.12 Beta 3. There will be a
+        archive. We used version 2.0.12. There will be a
         <literal>/web</literal> directory in the root of the deployment. Copy
         an <literal>applicationContext.xml</literal> containing your
         <literal>AuthenticationManager</literal> as well as the

+ 1 - 1
project.properties

@@ -6,7 +6,7 @@
 # $Id$
 
 # Project version
-acegi-security-version=0.51
+acegi-security-version=0.6
 
 # Project name
 name=acegi-security-system-for-spring

+ 1 - 0
samples/contacts/etc/ca/resin-acegisecurity.xml

@@ -33,6 +33,7 @@
 	<!-- Authentication provider that queries our data access object  -->
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
+     	<property name="forcePrincipalAsString"><value>true</value></property>
 	</bean>
 
 	<!-- The authentication manager that iterates through our only authentication provider -->

+ 8 - 1
samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java

@@ -19,6 +19,7 @@ import net.sf.acegisecurity.AccessDeniedException;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.context.ContextHolder;
 import net.sf.acegisecurity.context.SecureContext;
+import net.sf.acegisecurity.providers.dao.User;
 
 import org.springframework.beans.factory.InitializingBean;
 
@@ -88,7 +89,13 @@ public class ContactManagerFacade implements ContactManager, InitializingBean {
         Authentication auth = ((SecureContext) ContextHolder.getContext())
             .getAuthentication();
 
-        if (auth.getPrincipal().toString().equals(result.getOwner())) {
+        String username = auth.getPrincipal().toString();
+
+        if (auth.getPrincipal() instanceof User) {
+            username = ((User) auth.getPrincipal()).getUsername();
+        }
+
+        if (username.equals(result.getOwner())) {
             return result;
         } else {
             throw new AccessDeniedException(

+ 9 - 2
samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java

@@ -18,6 +18,7 @@ package sample.contact;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.ConfigAttribute;
 import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.providers.dao.User;
 import net.sf.acegisecurity.vote.AccessDecisionVoter;
 
 import org.aopalliance.intercept.MethodInvocation;
@@ -96,9 +97,15 @@ public class ContactSecurityVoter implements AccessDecisionVoter {
                 }
 
                 if (passedOwner != null) {
+                    String username = authentication.getPrincipal().toString();
+
+                    if (authentication.getPrincipal() instanceof User) {
+                        username = ((User) authentication.getPrincipal())
+                            .getUsername();
+                    }
+
                     // Check the authentication principal matches the passed owner
-                    if (passedOwner.equals(authentication.getPrincipal()
-                                                         .toString())) {
+                    if (passedOwner.equals(username)) {
                         return ACCESS_GRANTED;
                     }
                 }

+ 12 - 5
samples/contacts/src/main/java/sample/contact/SecureIndexController.java

@@ -20,6 +20,7 @@ import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
 import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.context.ContextHolder;
 import net.sf.acegisecurity.context.SecureContext;
+import net.sf.acegisecurity.providers.dao.User;
 
 import org.springframework.beans.factory.InitializingBean;
 
@@ -74,10 +75,17 @@ public class SecureIndexController implements Controller, InitializingBean {
                 + "SecureContext");
         }
 
-        final Authentication currentUser = secureContext.getAuthentication();
+        // Lookup username. As we must accommodate DaoAuthenticationProvider,
+        // CAS and container based authentication, we take care with casting
+        Authentication auth = secureContext.getAuthentication();
+        String username = auth.getPrincipal().toString();
+
+        if (auth.getPrincipal() instanceof User) {
+            username = ((User) auth.getPrincipal()).getUsername();
+        }
 
         boolean supervisor = false;
-        GrantedAuthority[] granted = currentUser.getAuthorities();
+        GrantedAuthority[] granted = auth.getAuthorities();
 
         for (int i = 0; i < granted.length; i++) {
             if (granted[i].getAuthority().equals("ROLE_SUPERVISOR")) {
@@ -85,13 +93,12 @@ public class SecureIndexController implements Controller, InitializingBean {
             }
         }
 
-        Contact[] myContacts = contactManager.getAllByOwner(currentUser.getPrincipal()
-                                                                       .toString());
+        Contact[] myContacts = contactManager.getAllByOwner(username);
 
         Map model = new HashMap();
         model.put("contacts", myContacts);
         model.put("supervisor", new Boolean(supervisor));
-        model.put("user", currentUser.getPrincipal().toString());
+        model.put("user", username);
 
         return new ModelAndView("index", "model", model);
     }

+ 10 - 2
samples/contacts/src/main/java/sample/contact/WebContactAddController.java

@@ -15,8 +15,10 @@
 
 package sample.contact;
 
+import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.context.ContextHolder;
 import net.sf.acegisecurity.context.SecureContext;
+import net.sf.acegisecurity.providers.dao.User;
 
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.SimpleFormController;
@@ -54,8 +56,14 @@ public class WebContactAddController extends SimpleFormController {
     public ModelAndView onSubmit(Object command) throws ServletException {
         String name = ((WebContact) command).getName();
         String email = ((WebContact) command).getEmail();
-        String owner = ((SecureContext) ContextHolder.getContext()).getAuthentication()
-                        .getPrincipal().toString();
+
+        Authentication auth = ((SecureContext) ContextHolder.getContext())
+            .getAuthentication();
+        String owner = auth.getPrincipal().toString();
+
+        if (auth.getPrincipal() instanceof User) {
+            owner = ((User) auth.getPrincipal()).getUsername();
+        }
 
         Contact contact = new Contact(contactManager.getNextId(), name, email,
                 owner);

+ 27 - 0
upgrade-05-06.txt

@@ -0,0 +1,27 @@
+===============================================================================
+          ACEGI SECURITY SYSTEM FOR SPRING - UPGRADING FROM 0.5 TO 0.6
+===============================================================================
+
+The following should help most casual users of the project update their
+applications:
+
+- Locate and remove all property references to 
+  DaoAuthenticationProvider.key and 
+  DaoAuthenticationProvider.refreshTokenInterval.
+
+- If you are using DaoAuthenticationProvider and either (i) you are using
+  container adapters or (ii) your code relies on the Authentication object
+  having its getPrincipal() return a String, you must set the new
+  DaoAuthenticationProvider property, forcePrincipalAsString, to true.
+  By default DaoAuthenticationProvider returns an Authentication object
+  containing the relevant User, which allows access to additional properties.
+  Where possible, we recommend you change your code to something like this,
+  so that you can leave forcePrincipalAsString to the false default:
+  
+    String username = authentication.getPrincipal();
+    if (authentication.getPrincipal() instanceof User) {
+      username = ((User) authentication.getPrincipal()).getUsername();
+    }
+
+
+$Id$