diff --git a/changelog.txt b/changelog.txt index fcc6efc11b..5b666ff215 100644 --- a/changelog.txt +++ b/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) ------------------------------------ diff --git a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java index 8b1036c34a..ea204b93a3 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java @@ -51,12 +51,26 @@ import org.springframework.dao.DataAccessException; *

* Upon successful validation, a * UsernamePasswordAuthenticationToken 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 + * String representation of the username, or the {@link User} + * that was returned from the authentication repository. Using + * String is appropriate if a container adapter is being used, as + * it expects String representations of the username. Using + * User 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 User provides additional flexibility, by default a + * User is returned. To override this default, set the {@link + * #setForcePrincipalAsString} to true. + *

+ * + *

+ * Caching is handled via the User 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. *

* *

@@ -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) { diff --git a/core/src/main/resources/org/acegisecurity/adapters/acegisecurity.xml b/core/src/main/resources/org/acegisecurity/adapters/acegisecurity.xml index 38a23ee9ca..0c3ce24b6e 100644 --- a/core/src/main/resources/org/acegisecurity/adapters/acegisecurity.xml +++ b/core/src/main/resources/org/acegisecurity/adapters/acegisecurity.xml @@ -36,6 +36,7 @@ + true diff --git a/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml b/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml index 38a23ee9ca..0c3ce24b6e 100644 --- a/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml +++ b/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml @@ -36,6 +36,7 @@ + true diff --git a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java index 8898aaf240..1e2a37fcfe 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java +++ b/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()); diff --git a/core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java index a1fa7dede5..1f3c8fa451 100644 --- a/core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java +++ b/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 diff --git a/core/src/test/java/org/acegisecurity/ui/cas/CasProcessingFilterEntryPointTests.java b/core/src/test/java/org/acegisecurity/ui/cas/CasProcessingFilterEntryPointTests.java index f3444b3a6c..1cc674e0bc 100644 --- a/core/src/test/java/org/acegisecurity/ui/cas/CasProcessingFilterEntryPointTests.java +++ b/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 { diff --git a/docs/reference/src/index.xml b/docs/reference/src/index.xml index f026126927..0d7027008b 100644 --- a/docs/reference/src/index.xml +++ b/docs/reference/src/index.xml @@ -7,7 +7,7 @@ Reference Documentation - 0.51 + 0.6 @@ -946,10 +946,24 @@ increased the complexity of the AuthenticationDao 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 - AuthenticationManager or - AuthenticationProvider implementation if it were - desired. + could be easily provided by leveraging the application event + publishing features discussed below. + + DaoAuthenticationProvider returns an + Authentication object which in turn has its + principal property set. The principal will be + either a String (which is essentially the username) + or a User object (which was looked up from the + AuthenticationDao). By default the + User is returned, as this enables applications to + subclass User 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 Strings (as was the case for + releases prior to Acegi Security 0.6), you should set the + DaoAuthenticationProvider.forcePrincipalAsString + property to true in your application + context. @@ -1927,6 +1941,11 @@ public boolean supports(Class clazz); provided below. Once installed, please take the time to try the sample application to ensure your container adapter is properly configured. + + When using container adapters with the + DaoAuthenticationProvider, ensure you set its + forcePrincipalAsString property to + true. @@ -2497,7 +2516,7 @@ $CATALINA_HOME/bin/startup.sh PasswordHandler will do). 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 /web directory in the root of the deployment. Copy an applicationContext.xml containing your AuthenticationManager as well as the diff --git a/project.properties b/project.properties index 9490cef1b2..cfb572d789 100644 --- a/project.properties +++ b/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 diff --git a/samples/contacts/etc/ca/resin-acegisecurity.xml b/samples/contacts/etc/ca/resin-acegisecurity.xml index 38175dc397..90280d781f 100644 --- a/samples/contacts/etc/ca/resin-acegisecurity.xml +++ b/samples/contacts/etc/ca/resin-acegisecurity.xml @@ -33,6 +33,7 @@ + true diff --git a/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java b/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java index 38fcf9e046..4cc23dd6a5 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java +++ b/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( diff --git a/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java b/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java index 98cc029834..792f49697d 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java +++ b/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; } } diff --git a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java b/samples/contacts/src/main/java/sample/contact/SecureIndexController.java index a651203e7a..ea75a074fb 100644 --- a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java +++ b/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); } diff --git a/samples/contacts/src/main/java/sample/contact/WebContactAddController.java b/samples/contacts/src/main/java/sample/contact/WebContactAddController.java index 3a3cbb89e8..24c7138f44 100644 --- a/samples/contacts/src/main/java/sample/contact/WebContactAddController.java +++ b/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); diff --git a/upgrade-05-06.txt b/upgrade-05-06.txt new file mode 100644 index 0000000000..5a351f4edb --- /dev/null +++ b/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$