From fee85fe53cf71a1a708dfef071b01884c20fa41f Mon Sep 17 00:00:00 2001
From: Daniel Gerhardt <code@dgerhardt.net>
Date: Wed, 11 May 2016 15:12:15 +0200
Subject: [PATCH] Fix case-insensitive handling of LDAP user IDs

---
 .../de/thm/arsnova/config/SecurityConfig.java | 15 +++++++-
 .../arsnova/controller/LoginController.java   |  2 +-
 .../java/de/thm/arsnova/entities/User.java    |  1 +
 .../security/CustomLdapUserDetailsMapper.java | 36 +++++++++++++++++++
 4 files changed, 52 insertions(+), 2 deletions(-)
 create mode 100644 src/main/java/de/thm/arsnova/security/CustomLdapUserDetailsMapper.java

diff --git a/src/main/java/de/thm/arsnova/config/SecurityConfig.java b/src/main/java/de/thm/arsnova/config/SecurityConfig.java
index 6ca21c5a..564f483d 100644
--- a/src/main/java/de/thm/arsnova/config/SecurityConfig.java
+++ b/src/main/java/de/thm/arsnova/config/SecurityConfig.java
@@ -25,6 +25,7 @@ import de.thm.arsnova.CasUserDetailsService;
 import de.thm.arsnova.LoginAuthenticationFailureHandler;
 import de.thm.arsnova.LoginAuthenticationSucessHandler;
 import de.thm.arsnova.security.ApplicationPermissionEvaluator;
+import de.thm.arsnova.security.CustomLdapUserDetailsMapper;
 import de.thm.arsnova.security.DbUserDetailsService;
 import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
 import org.scribe.up.provider.impl.FacebookProvider;
@@ -65,6 +66,7 @@ import org.springframework.security.ldap.authentication.LdapAuthenticator;
 import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
 import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
 import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
+import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
 import org.springframework.security.web.authentication.logout.LogoutFilter;
@@ -96,6 +98,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter implements Serv
 
 	@Value("${security.ldap.enabled}") private boolean ldapEnabled;
 	@Value("${security.ldap.url}") private String ldapUrl;
+	@Value("${security.ldap.user-id-attr:uid}") private String ldapUserIdAttr;
 	@Value("${security.ldap.user-dn-pattern:}") private String ldapUserDn;
 	@Value("${security.ldap.user-search-base:}") private String ldapSearchBase;
 	@Value("${security.ldap.user-search-filter:}") private String ldapSearchFilter;
@@ -249,7 +252,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter implements Serv
 
 	@Bean
 	public LdapAuthenticationProvider ldapAuthenticationProvider() throws Exception {
-		return new LdapAuthenticationProvider(ldapAuthenticator(), ldapAuthoritiesPopulator());
+		LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapAuthenticator(), ldapAuthoritiesPopulator());
+		ldapAuthenticationProvider.setUserDetailsContextMapper(customLdapUserDetailsMapper());
+
+		return ldapAuthenticationProvider;
 	}
 
 	@Bean
@@ -284,6 +290,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter implements Serv
 		return new DefaultLdapAuthoritiesPopulator(ldapContextSource(), null);
 	}
 
+	@Bean
+	public LdapUserDetailsMapper customLdapUserDetailsMapper() {
+		logger.debug("ldapUserIdAttr: {}", ldapUserIdAttr);
+
+		return new CustomLdapUserDetailsMapper(ldapUserIdAttr);
+	}
+
 	// CAS Authentication Configuration
 
 	@Bean
diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java
index 92cf3257..84436163 100644
--- a/src/main/java/de/thm/arsnova/controller/LoginController.java
+++ b/src/main/java/de/thm/arsnova/controller/LoginController.java
@@ -183,7 +183,7 @@ public class LoginController extends AbstractController {
 				try {
 					Authentication auth = ldapAuthenticationProvider.authenticate(token);
 					if (auth.isAuthenticated()) {
-						SecurityContextHolder.getContext().setAuthentication(token);
+						SecurityContextHolder.getContext().setAuthentication(auth);
 						request.getSession(true).setAttribute(
 								HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
 								SecurityContextHolder.getContext());
diff --git a/src/main/java/de/thm/arsnova/entities/User.java b/src/main/java/de/thm/arsnova/entities/User.java
index dacb2294..938a1fe6 100644
--- a/src/main/java/de/thm/arsnova/entities/User.java
+++ b/src/main/java/de/thm/arsnova/entities/User.java
@@ -24,6 +24,7 @@ import org.scribe.up.profile.google.Google2Profile;
 import org.scribe.up.profile.twitter.TwitterProfile;
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
 
 import java.io.Serializable;
 
diff --git a/src/main/java/de/thm/arsnova/security/CustomLdapUserDetailsMapper.java b/src/main/java/de/thm/arsnova/security/CustomLdapUserDetailsMapper.java
new file mode 100644
index 00000000..13339d0a
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/security/CustomLdapUserDetailsMapper.java
@@ -0,0 +1,36 @@
+package de.thm.arsnova.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
+
+import java.util.Collection;
+
+/**
+ * Replaces the user ID provided by the authenticating user with the one that is part of LDAP object. This is necessary
+ * to get a consistent ID despite case insensitivity.
+ */
+public class CustomLdapUserDetailsMapper extends LdapUserDetailsMapper {
+	public static final Logger LOGGER = LoggerFactory.getLogger(CustomLdapUserDetailsMapper.class);
+
+	private String userIdAttr;
+
+	public CustomLdapUserDetailsMapper(String ldapUserIdAttr) {
+		this.userIdAttr = ldapUserIdAttr;
+	}
+
+	public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
+										  Collection<? extends GrantedAuthority> authorities) {
+		String ldapUsername = ctx.getStringAttribute(userIdAttr);
+		if (ldapUsername == null) {
+			LOGGER.warn("LDAP attribute {} not set. Falling back to user provided username.", userIdAttr);
+			ldapUsername = username;
+		}
+		UserDetails userDetails = super.mapUserFromContext(ctx, ldapUsername, authorities);
+
+		return userDetails;
+	}
+}
-- 
GitLab