diff --git a/pom.xml b/pom.xml index 76386fdaa560a58fd9750b3fc04f3ac6a00a35e3..9888107480ccb9ca0cb3a4c117af652820303740 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>edu.wisc.uwss</groupId> <artifactId>uw-spring-security</artifactId> - <version>1.1.1-SNAPSHOT</version> + <version>1.2.0-SNAPSHOT</version> <packaging>pom</packaging> <name>UW Spring Security Parent</name> <description>Parent project for module to integrate Spring Security with UW authentication mechanism.</description> diff --git a/uw-spring-security-config/pom.xml b/uw-spring-security-config/pom.xml index 150d1ecfd237c1205bc7581e05ff35f11a80abb4..093d2a150c5d28e4e813680489bba54bb5d69e2f 100644 --- a/uw-spring-security-config/pom.xml +++ b/uw-spring-security-config/pom.xml @@ -3,7 +3,7 @@ <parent> <groupId>edu.wisc.uwss</groupId> <artifactId>uw-spring-security</artifactId> - <version>1.1.1-SNAPSHOT</version> + <version>1.2.0-SNAPSHOT</version> </parent> <artifactId>uw-spring-security-config</artifactId> <name>UW Spring Security Configuration</name> diff --git a/uw-spring-security-core/pom.xml b/uw-spring-security-core/pom.xml index ad1f4aa3bbb79b707c4b5c26aad5dbcc445fc204..01f3d8fa31f2d44be1339af208d431b604cdaa0f 100644 --- a/uw-spring-security-core/pom.xml +++ b/uw-spring-security-core/pom.xml @@ -3,7 +3,7 @@ <parent> <groupId>edu.wisc.uwss</groupId> <artifactId>uw-spring-security</artifactId> - <version>1.1.1-SNAPSHOT</version> + <version>1.2.0-SNAPSHOT</version> </parent> <artifactId>uw-spring-security-core</artifactId> <name>UW Spring Security Core</name> diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsAttributesMapper.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsAttributesMapper.java index db33acb8ca0dec3febf7d977069fd6220928c49c..751cde911439b9ef23622a5ead0e4d4fa88b617b 100644 --- a/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsAttributesMapper.java +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsAttributesMapper.java @@ -13,6 +13,15 @@ import edu.wisc.uwss.UWUserDetailsImpl; /** * Interface providing a mechanism to bind a row from the local users properties * file to a {@link UWUserDetails} instance. + * + * This interface is used during application initialization - not during + * authentication attempts. Since it is executed during Spring ApplicationContext initialization, + * implementations should avoid injecting other service or dao interfaces, as it may be + * affected by a race condition. + * + * If you have custom {@link UWUserDetails} that depend on services/daos to complete the model, + * you may want to consider implementing a {@link LocalUsersAuthenticationAttemptCallback}; that interface + * participates in the authentication attempt itself, not during application initialization. * * @author Nicholas Blair */ diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsManagerImpl.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsManagerImpl.java index 9985a05c0f0ffdbd1f6b4a0dbb54bbfb4f97a6a7..e331dae10c6dbe349f90f5cf604c0aa6ffa49f3d 100644 --- a/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsManagerImpl.java +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsManagerImpl.java @@ -3,6 +3,8 @@ */ package edu.wisc.uwss.local; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; @@ -39,6 +41,8 @@ public class LocalUserDetailsManagerImpl implements UserDetailsManager { private Logger logger = LoggerFactory.getLogger(this.getClass()); private final Map<String, UWUserDetails> users = new ConcurrentHashMap<>(); + @Autowired(required=false) + private List<LocalUsersAuthenticationAttemptCallback> callbacks = new ArrayList<>(); @Autowired(required=false) private LocalUserDetailsAttributesMapper localUserDetailsAttributeMapper = new LocalUserDetailsAttributesMapper.Default(); @@ -68,6 +72,16 @@ public class LocalUserDetailsManagerImpl implements UserDetailsManager { public void setDemoUsers(@Qualifier("demo-users") Properties properties) { this.properties = properties; } + + /** + * Visible for testing. + * + * @param callbacks + */ + void setCallbacks(List<LocalUsersAuthenticationAttemptCallback> callbacks) { + this.callbacks = callbacks; + } + /** * Add a user. Does nothing if the argument is null or the {@link UWUserDetailsImpl#getUsername()} is blank. * @@ -109,7 +123,11 @@ public class LocalUserDetailsManagerImpl implements UserDetailsManager { if(user == null) { throw new UsernameNotFoundException(username + " not found"); } - return SerializationUtils.clone(user); + UWUserDetails result = SerializationUtils.clone(user); + for(LocalUsersAuthenticationAttemptCallback callback: callbacks) { + callback.success(result); + } + return result; } /** * @param user diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUsersAuthenticationAttemptCallback.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUsersAuthenticationAttemptCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..f862109e514a70df950f2ec50cf246bc3486d209 --- /dev/null +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUsersAuthenticationAttemptCallback.java @@ -0,0 +1,39 @@ +package edu.wisc.uwss.local; + +import edu.wisc.uwss.UWUserDetails; + +/** + * Callback interface allowing downstream projects to mutate the {@link UWUserDetails} returned + * by {@link LocalUserDetailsManagerImpl#loadUserByUsername(String)} during an authentication attempt. + * + * Example usage: + * + <pre> + class MyLocalUsersAuthenticationAttemptCallback implements LocalUsersAuthenticationAttemptCallback<MyUWUserDetailsImpl> { + + @Autowired + private MySomethingDao somethingDao; + + public void success(MyUWUserDetailsImpl userDetails) { + userDetails.setMySomethingField(somethingDao.getSomethingsForUser(userDetails.getUsername()); + } + } + </pre> + * + * Consumers can register any number of Beans implementing this interface; see {@link org.springframework.core.Ordered} + * if you need a particular order of execution. + * + * If instead you need to modify {@link UWUserDetails} instances during application initialization only, + * see {@link LocalUserDetailsAttributesMapper}. + * + * @author Nicholas Blair + */ +public interface LocalUsersAuthenticationAttemptCallback<T extends UWUserDetails> { + + /** + * Callback executed upon successfully loading the {@link UWUserDetails} for a username. + * + * @param userDetails the userDetails to modify + */ + void success(T userDetails); +} diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java index 7684fdaf1683b580424367149836fdb83a2d0036..1dce635642ffcb9a533393cce02c841703488bc8 100644 --- a/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/preauth/PreauthenticatedUserDetailsAttributeMapper.java @@ -23,6 +23,9 @@ import edu.wisc.uwss.UWUserDetailsImpl; /** * Interface providing a mechanism to the attributes preauthenticated in the * {@link HttpServletRequest} to a {@link UWUserDetails} instance. + * + * This interface is executed during authentication attempts (similar to + * {@link edu.wisc.uwss.local.LocalUsersAuthenticationAttemptCallback}). * * @author Nicholas Blair */ diff --git a/uw-spring-security-core/src/test/java/edu/wisc/uwss/local/LocalUserDetailsManagerImplTest.java b/uw-spring-security-core/src/test/java/edu/wisc/uwss/local/LocalUserDetailsManagerImplTest.java index b07c9e6d07efa3b0d31f85b7dee2e8da3e3bbfb5..405ccf609aec0c379cb73b5128f3f0f34fbacc3d 100644 --- a/uw-spring-security-core/src/test/java/edu/wisc/uwss/local/LocalUserDetailsManagerImplTest.java +++ b/uw-spring-security-core/src/test/java/edu/wisc/uwss/local/LocalUserDetailsManagerImplTest.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.Properties; @@ -30,7 +31,7 @@ import edu.wisc.uwss.UWUserDetailsImpl; public class LocalUserDetailsManagerImplTest { /** - * Verify {@link LocalUserDetailsServiceImpl#loadUserByUsername(String)} throws + * Verify {@link LocalUserDetailsManagerImpl#loadUserByUsername(String)} throws * {@link UsernameNotFoundException} for an unknown user. */ @Test(expected=UsernameNotFoundException.class) @@ -275,5 +276,32 @@ public class LocalUserDetailsManagerImplTest { manager.deleteUser(username); assertFalse(manager.userExists(username)); } + + /** + * Test confirming {@link LocalUsersAuthenticationAttemptCallback} is fired appropriately + * during an authentication attempt ({@link LocalUserDetailsManagerImpl#loadUserByUsername(String)}). + */ + @Test + public void loadUserByUsername_with_callback() { + LocalUserDetailsManagerImpl manager = new LocalUserDetailsManagerImpl(); + // setup a simple callback to mutate the userDetails + LocalUsersAuthenticationAttemptCallback callback = new LocalUsersAuthenticationAttemptCallback<UWUserDetailsImpl>() { + @Override + public void success(UWUserDetailsImpl userDetails) { + userDetails.setFirstName("something custom"); + } + }; + manager.setCallbacks(Arrays.asList(callback)); + + // no first name set on original user + UWUserDetails user = new UWUserDetailsImpl("UW000A000", "foo", "bar", "Foo Bar", "foo@foo.wisc.edu"); + // first name starts null + assertNull(user.getFirstName()); + manager.addDemoUser(user); + + UWUserDetails result = manager.loadUserByUsername("foo"); + // observe firstname modified by the callback + assertEquals("something custom", result.getFirstName()); + } } \ No newline at end of file diff --git a/uw-spring-security-sample-war/pom.xml b/uw-spring-security-sample-war/pom.xml index dd52b4756ae52f916f101252e1ac6b9f8e8cf6c0..be90c32fad2a3b16c226e0ebd3f6871bb74d7131 100644 --- a/uw-spring-security-sample-war/pom.xml +++ b/uw-spring-security-sample-war/pom.xml @@ -3,7 +3,7 @@ <parent> <groupId>edu.wisc.uwss</groupId> <artifactId>uw-spring-security</artifactId> - <version>1.1.1-SNAPSHOT</version> + <version>1.2.0-SNAPSHOT</version> </parent> <artifactId>uw-spring-security-sample-war</artifactId> <name>UW Spring Security Sample War</name> diff --git a/uw-spring-security-web/pom.xml b/uw-spring-security-web/pom.xml index 4db1c09a5f0cb2f6f93b72b9d162c1e0b8af71e2..4f9a7f95b9d22a2d634356213479b3b006707e87 100644 --- a/uw-spring-security-web/pom.xml +++ b/uw-spring-security-web/pom.xml @@ -3,7 +3,7 @@ <parent> <groupId>edu.wisc.uwss</groupId> <artifactId>uw-spring-security</artifactId> - <version>1.1.1-SNAPSHOT</version> + <version>1.2.0-SNAPSHOT</version> </parent> <artifactId>uw-spring-security-web</artifactId> <name>UW Spring Security Web</name>