From 89c32e9f161d343455c50d22ad6f4d70f4807a04 Mon Sep 17 00:00:00 2001 From: Nicholas Blair <nicholas.blair@wisc.edu> Date: Tue, 17 May 2016 20:03:23 -0500 Subject: [PATCH] feat: add callback for UserDetailsService#loadUserByUsername Allows downstream projects to mutate the UWUserDetails instance returned by that core method. --- .../local/LocalUWUserDetailsCallback.java | 36 +++++++++++++++++++ .../local/LocalUserDetailsManagerImpl.java | 20 ++++++++++- .../LocalUserDetailsManagerImplTest.java | 25 ++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUWUserDetailsCallback.java diff --git a/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUWUserDetailsCallback.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUWUserDetailsCallback.java new file mode 100644 index 0000000..a12b41f --- /dev/null +++ b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUWUserDetailsCallback.java @@ -0,0 +1,36 @@ +package edu.wisc.uwss.local; + +import edu.wisc.uwss.UWUserDetails; + +/** + * Callback interface allowing downstream projects to mutate the result to be returned from + * {@link LocalUserDetailsManagerImpl#loadUserByUsername(String)}. + * + * Example usage: + * + <pre> + class MyCustomLocalUWUserDetailsCallback implements LocalUWUserDetailsCallback<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. + * + * @author Nicholas Blair + */ +public interface LocalUWUserDetailsCallback<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/local/LocalUserDetailsManagerImpl.java b/uw-spring-security-core/src/main/java/edu/wisc/uwss/local/LocalUserDetailsManagerImpl.java index 9985a05..f657dd6 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<LocalUWUserDetailsCallback> 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<LocalUWUserDetailsCallback> 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(LocalUWUserDetailsCallback callback: callbacks) { + callback.success(result); + } + return result; } /** * @param user 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 b07c9e6..c62f96f 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,27 @@ public class LocalUserDetailsManagerImplTest { manager.deleteUser(username); assertFalse(manager.userExists(username)); } + + @Test + public void loadUserByUsername_with_callback() { + boolean visited = false; + LocalUserDetailsManagerImpl manager = new LocalUserDetailsManagerImpl(); + LocalUWUserDetailsCallback callback = new LocalUWUserDetailsCallback<UWUserDetailsImpl>() { + @Override + public void success(UWUserDetailsImpl userDetails) { + userDetails.setFirstName("something custom"); + } + }; + manager.setCallbacks(Arrays.asList(callback)); + + 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 -- GitLab