[New] Refactored LdapPrivilegeHandler to create JsonConfigLdapPrivilegeHandler

This commit is contained in:
Robert von Burg 2019-04-11 15:40:26 +02:00
parent 4b673e58e6
commit 223f7fa79e
6 changed files with 365 additions and 84 deletions

View File

@ -33,6 +33,11 @@
<artifactId>li.strolch.utils</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>

View File

@ -10,5 +10,7 @@ public class PrivilegeConstants {
public static final String REALM = "realm";
public static final String LOCATION = "location";
public static final String DEFAULT_LOCATION = "defaultLocation";
public static final String ROLES = "roles";
public static final String EMAIL = "email";
}

View File

@ -4,45 +4,39 @@ import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.util.*;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.InvalidCredentialsException;
import li.strolch.privilege.model.UserState;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.policy.PrivilegePolicy;
import li.strolch.utils.dbc.DBC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LdapPrivilegeHandler extends DefaultPrivilegeHandler {
public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler {
protected static final Logger logger = LoggerFactory.getLogger(LdapPrivilegeHandler.class);
protected static final Logger logger = LoggerFactory.getLogger(BaseLdapPrivilegeHandler.class);
private Map<String, Set<String>> rolesForLdapGroups;
private String providerUrl;
private String searchBase;
private String location;
private String domain;
private String adminUsers;
@Override
public synchronized void initialize(Map<String, String> parameterMap, EncryptionHandler encryptionHandler,
PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler,
SingleSignOnHandler ssoHandler, Map<String, Class<PrivilegePolicy>> policyMap) {
rolesForLdapGroups = getLdapGroupToRolesMappingFromConfig(parameterMap);
super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler,
policyMap);
this.providerUrl = parameterMap.get("providerUrl");
this.searchBase = parameterMap.get("searchBase");
this.location = parameterMap.get("location");
this.domain = parameterMap.get("domain");
this.adminUsers = parameterMap.get("adminUsers");
super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler,
policyMap);
}
@Override
@ -67,9 +61,6 @@ public class LdapPrivilegeHandler extends DefaultPrivilegeHandler {
logger.info("User {} tries to login on ldap", username + this.domain);
String memberOfLdapString = "";
Set<String> strolchRoles = new HashSet<>();
// Create the initial context
DirContext ctx = null;
try {
@ -98,58 +89,12 @@ public class LdapPrivilegeHandler extends DefaultPrivilegeHandler {
+ " on Ldap: no LDAP Data, for either userPrincipalName or sAMAccountName");
}
SearchResult sr = (SearchResult) answer.next();
SearchResult searchResult = answer.next();
if (answer.hasMore())
throw new AccessDeniedException(
"Could not login with user: " + username + this.domain + " on Ldap: Multiple LDAP Data");
Attributes attrs = sr.getAttributes();
Attribute sAMAccountName = attrs.get("sAMAccountName");
if (sAMAccountName == null || !username.equals(sAMAccountName.get().toString()))
throw new AccessDeniedException(
"Could not login with user: " + username + this.domain + " on Ldap: Wrong LDAP Data");
Attribute givenName = attrs.get("givenName");
Attribute sn = attrs.get("sn");
String firstName = givenName == null ? username : givenName.get().toString();
String lastName = sn == null ? username : sn.get().toString();
// evaluate roles for this user
Attribute groupMembers = attrs.get("memberOf");
logger.info("User " + username + " is member of groups: ");
if (groupMembers != null) {
for (int i = 0; i < groupMembers.size(); i++) {
memberOfLdapString = attrs.get("memberOf").get(i).toString();
// extract group name from ldap string -> CN=groupname,OU=company,DC=domain,DC=country
LdapName memberOfName = new LdapName(memberOfLdapString);
for (Rdn rdn : memberOfName.getRdns()) {
if (rdn.getType().equalsIgnoreCase("CN")) {
String groupName = rdn.getValue().toString();
logger.info(" - " + groupName);
Set<String> foundStrolchRoles = this.rolesForLdapGroups.get(groupName);
if (foundStrolchRoles != null)
strolchRoles.addAll(foundStrolchRoles);
break;
}
}
}
}
Map<String, String> properties = new HashMap<>();
// this must be changed, because the location param must be taken from the logged in person
properties.put("location", this.location);
// see if this is an admin user
if (this.adminUsers.contains(username))
strolchRoles = this.rolesForLdapGroups.get("admin");
User user = new User(username, username, null, null, null, -1, -1, firstName, lastName, UserState.REMOTE,
strolchRoles, Locale.GERMAN, properties);
User user = buildUserFromSearchResult(username, searchResult);
// persist this user
if (internalUser == null)
@ -163,8 +108,8 @@ public class LdapPrivilegeHandler extends DefaultPrivilegeHandler {
return user;
} catch (Exception e) {
logger.error("Could not login with user: " + username + domain + " on Ldap", e);
throw new AccessDeniedException("Could not login with user: " + username + domain + " on Ldap", e);
logger.error("Could not login with user: " + username + this.domain + " on Ldap", e);
throw new AccessDeniedException("Could not login with user: " + username + this.domain + " on Ldap", e);
} finally {
if (ctx != null) {
try {
@ -176,28 +121,49 @@ public class LdapPrivilegeHandler extends DefaultPrivilegeHandler {
}
}
private Map<String, Set<String>> getLdapGroupToRolesMappingFromConfig(Map<String, String> params) {
Map<String, Set<String>> result = new HashMap<>();
protected User buildUserFromSearchResult(String username, SearchResult sr) throws NamingException {
Attributes attrs = sr.getAttributes();
String rolesForLdapGroups = params.get("rolesForLdapGroups");
validateLdapUsername(username, attrs);
DBC.PRE.assertNotEmpty("No roles mapping for ldap directory groups defined (param: rolesForLdapGroups)",
rolesForLdapGroups);
String firstName = getFirstName(username, attrs);
String lastName = getLastName(username, attrs);
Locale locale = getLocale(attrs);
// rolesForLdapGroups = admin=StrolchAdmin,UserPrivileges;user=UserPrivileges
String[] ldapGroupRoles = rolesForLdapGroups.split(";");
// evaluate roles for this user
Set<String> ldapGroups = getLdapGroups(username, attrs);
logger.info("User " + username + " is member of the following LDAP groups: ");
ldapGroups.forEach(s -> logger.info("- " + s));
Set<String> strolchRoles = mapToStrolchRoles(username, ldapGroups);
for (String ldapGroupRole : ldapGroupRoles) {
// admin=StrolchAdmin,UserPrivileges
String[] splittedGroupRoles = ldapGroupRole.split("=");
String ldapGroupName = splittedGroupRoles[0];
// StrolchAdmin,UserPrivileges
Set<String> roleNames = new HashSet<>(Arrays.asList(splittedGroupRoles[1].split(",")));
Map<String, String> properties = buildProperties(username, attrs, ldapGroups, strolchRoles);
result.put(ldapGroupName, roleNames);
}
return result;
return new User(username, username, null, null, null, -1, -1, firstName, lastName, UserState.REMOTE,
strolchRoles, locale, properties);
}
protected abstract Map<String, String> buildProperties(String username, Attributes attrs, Set<String> ldapGroups,
Set<String> strolchRoles) throws NamingException;
protected void validateLdapUsername(String username, Attributes attrs) throws NamingException {
Attribute sAMAccountName = attrs.get("sAMAccountName");
if (sAMAccountName == null || !username.equals(sAMAccountName.get().toString()))
throw new AccessDeniedException(
"Could not login with user: " + username + this.domain + " on Ldap: Wrong LDAP Data");
}
protected String getLdapString(Attributes attrs, String key) throws NamingException {
Attribute sn = attrs.get(key);
return sn == null ? null : sn.get().toString();
}
protected abstract String getFirstName(String username, Attributes attrs) throws NamingException;
protected abstract String getLastName(String username, Attributes attrs) throws NamingException;
protected abstract Locale getLocale(Attributes attrs) throws NamingException;
protected abstract Set<String> getLdapGroups(String username, Attributes attrs) throws NamingException;
protected abstract Set<String> mapToStrolchRoles(String username, Set<String> ldapGroups);
}

View File

@ -0,0 +1,152 @@
package li.strolch.privilege.handler;
import static com.sun.xml.internal.fastinfoset.stax.events.Util.isEmptyString;
import static java.lang.String.join;
import static java.util.stream.Collectors.toSet;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import java.io.File;
import java.io.FileReader;
import java.util.*;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import li.strolch.privilege.helper.LdapHelper;
import li.strolch.privilege.policy.PrivilegePolicy;
import li.strolch.utils.dbc.DBC;
public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler {
private Locale defaultLocale;
private JsonObject configJ;
private Set<String> ldapGroupNames;
private String realm;
@Override
public synchronized void initialize(Map<String, String> parameterMap, EncryptionHandler encryptionHandler,
PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler,
SingleSignOnHandler ssoHandler, Map<String, Class<PrivilegePolicy>> policyMap) {
super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler,
policyMap);
this.realm = parameterMap.get(REALM);
DBC.PRE.assertNotEmpty("realm must be set!", realm);
this.defaultLocale = parameterMap.containsKey("defaultLocale") ?
Locale.forLanguageTag(parameterMap.get("defaultLocale")) :
Locale.getDefault();
String configFileS = parameterMap.get("configFile");
DBC.PRE.assertNotEmpty("configFile param must be set!", configFileS);
File configFile = new File(configFileS);
if (!configFile.exists() || !configFile.isFile() || !configFile.canRead())
throw new IllegalStateException(
"configFile does not exist, is not a file, or can not be read at path " + configFile
.getAbsolutePath());
// parse the configuration file
try (FileReader reader = new FileReader(configFile)) {
this.configJ = new JsonParser().parse(reader).getAsJsonObject();
} catch (Exception e) {
throw new IllegalStateException("Failed to read config file " + configFile.getAbsolutePath(), e);
}
// validate the configuration
this.ldapGroupNames = this.configJ.keySet();
if (this.ldapGroupNames.isEmpty())
throw new IllegalStateException(
"No LDAP group names are defined in config file " + configFile.getAbsolutePath());
// validate the configuration
for (String name : this.ldapGroupNames) {
JsonObject config = this.configJ.get(name).getAsJsonObject();
if (!config.has(LOCATION) || !config.get(LOCATION).isJsonArray()
|| config.get(LOCATION).getAsJsonArray().size() == 0)
throw new IllegalStateException("LDAP Group " + name
+ " is missing a location attribute, or it is not an array or the array is empty");
if (!config.has(LOCATION) || !config.get(LOCATION).isJsonArray()
|| config.get(LOCATION).getAsJsonArray().size() == 0)
throw new IllegalStateException("LDAP Group " + name
+ " is missing a roles attribute, or it is not an array or the array is empty");
}
}
@Override
protected String getFirstName(String username, Attributes attrs) throws NamingException {
String value = getLdapString(attrs, "givenName");
return isEmptyString(value) ? username : value;
}
@Override
protected String getLastName(String username, Attributes attrs) throws NamingException {
String value = getLdapString(attrs, "sn");
return isEmptyString(value) ? username : value;
}
@Override
protected Locale getLocale(Attributes attrs) throws NamingException {
return this.defaultLocale;
}
@Override
protected Set<String> getLdapGroups(String username, Attributes attrs) throws NamingException {
Set<String> ldapGroups = LdapHelper.getLdapGroups(attrs);
Set<String> relevantLdapGroups = ldapGroups.stream().filter(s -> this.ldapGroupNames.contains(s))
.collect(toSet());
if (relevantLdapGroups.isEmpty())
throw new IllegalStateException("User " + username
+ " can not login, as none of their LDAP Groups have mappings to Strolch Roles!");
if (relevantLdapGroups.size() > 1) {
logger.warn(
"User " + username + " has multiple relevant LDAP Groups which will lead to undefined behaviour: "
+ join(",", relevantLdapGroups));
}
return relevantLdapGroups;
}
@Override
protected Set<String> mapToStrolchRoles(String username, Set<String> ldapGroups) {
Set<String> strolchRoles = new HashSet<>();
for (String relevantLdapGroup : ldapGroups) {
JsonObject mappingJ = this.configJ.get(relevantLdapGroup).getAsJsonObject();
mappingJ.get(ROLES).getAsJsonArray().forEach(e -> strolchRoles.add(e.getAsString()));
}
return strolchRoles;
}
@Override
protected Map<String, String> buildProperties(String username, Attributes attrs, Set<String> ldapGroups,
Set<String> strolchRoles) throws NamingException {
String defaultLocation = "";
Set<String> locations = new HashSet<>();
for (String ldapGroup : ldapGroups) {
JsonObject mappingJ = this.configJ.get(ldapGroup).getAsJsonObject();
mappingJ.get(LOCATION).getAsJsonArray().forEach(e -> locations.add(e.getAsString()));
JsonElement defaultLocationJ = mappingJ.get(DEFAULT_LOCATION);
if (defaultLocationJ != null && !defaultLocationJ.isJsonNull()) {
if (!defaultLocation.isEmpty())
logger.warn("Default location already set by previous LDAP Group config, overriding for LDAP Group "
+ ldapGroup);
defaultLocation = defaultLocationJ.getAsString();
}
}
Map<String, String> properties = new HashMap<>();
properties.put(REALM, this.realm);
properties.put(LOCATION, join(",", locations));
properties.put(DEFAULT_LOCATION, defaultLocation);
return properties;
}
}

View File

@ -0,0 +1,121 @@
package li.strolch.privilege.handler;
import static com.sun.xml.internal.fastinfoset.stax.events.Util.isEmptyString;
import static li.strolch.privilege.base.PrivilegeConstants.LOCATION;
import static li.strolch.privilege.base.PrivilegeConstants.REALM;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import java.util.*;
import li.strolch.privilege.helper.LdapHelper;
import li.strolch.privilege.policy.PrivilegePolicy;
import li.strolch.utils.dbc.DBC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SimpleLdapPrivilegeHandler extends BaseLdapPrivilegeHandler {
protected static final Logger logger = LoggerFactory.getLogger(SimpleLdapPrivilegeHandler.class);
private Locale defaultLocale;
private String adminUsers;
private Map<String, Set<String>> rolesForLdapGroups;
private String location;
private String realm;
@Override
public synchronized void initialize(Map<String, String> parameterMap, EncryptionHandler encryptionHandler,
PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler,
SingleSignOnHandler ssoHandler, Map<String, Class<PrivilegePolicy>> policyMap) {
super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler,
policyMap);
this.location = parameterMap.getOrDefault(LOCATION, "");
this.realm = parameterMap.getOrDefault(REALM, "");
this.defaultLocale = parameterMap.containsKey("defaultLocale") ?
Locale.forLanguageTag(parameterMap.get("defaultLocale")) :
Locale.getDefault();
this.adminUsers = parameterMap.get("adminUsers");
this.rolesForLdapGroups = getLdapGroupToRolesMappingFromConfig(parameterMap);
}
@Override
protected String getFirstName(String username, Attributes attrs) throws NamingException {
String value = getLdapString(attrs, "givenName");
return isEmptyString(value) ? username : value;
}
@Override
protected String getLastName(String username, Attributes attrs) throws NamingException {
String value = getLdapString(attrs, "sn");
return isEmptyString(value) ? username : value;
}
@Override
protected Map<String, String> buildProperties(String username, Attributes attrs, Set<String> ldapGroups,
Set<String> strolchRoles) throws NamingException {
Map<String, String> properties = new HashMap<>();
properties.put(LOCATION, this.location);
properties.put(REALM, this.realm);
return properties;
}
@Override
protected Locale getLocale(Attributes attrs) {
return this.defaultLocale;
}
@Override
protected Set<String> getLdapGroups(String username, Attributes attrs) throws NamingException {
return LdapHelper.getLdapGroups(attrs);
}
@Override
protected Set<String> mapToStrolchRoles(String username, Set<String> ldapGroups) {
Set<String> strolchRoles = new HashSet<>();
for (String ldapRole : ldapGroups) {
Set<String> foundStrolchRoles = this.rolesForLdapGroups.get(ldapRole);
if (foundStrolchRoles != null)
strolchRoles.addAll(foundStrolchRoles);
}
// see if this is an admin user
if (this.adminUsers.contains(username))
strolchRoles = this.rolesForLdapGroups.get("admin");
return strolchRoles;
}
private Map<String, Set<String>> getLdapGroupToRolesMappingFromConfig(Map<String, String> params) {
String rolesForLdapGroups = params.get("rolesForLdapGroups");
DBC.PRE.assertNotEmpty("No roles mapping for ldap directory groups defined (param: rolesForLdapGroups)",
rolesForLdapGroups);
// rolesForLdapGroups = admin=StrolchAdmin,UserPrivileges;user=UserPrivileges
String[] ldapGroupRoles = rolesForLdapGroups.split(";");
Map<String, Set<String>> result = new HashMap<>();
for (String ldapGroupRole : ldapGroupRoles) {
ldapGroupRole = ldapGroupRole.trim();
String[] splitGroupRoles = ldapGroupRole.split("=");
String ldapGroupName = splitGroupRoles[0];
String[] strolchRoles = splitGroupRoles[1].split(",");
Set<String> roleNames = new HashSet<>();
for (String strolchRole : strolchRoles) {
roleNames.add(strolchRole.trim());
}
result.put(ldapGroupName, roleNames);
}
return result;
}
}

View File

@ -0,0 +1,35 @@
package li.strolch.privilege.helper;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.util.HashSet;
import java.util.Set;
public class LdapHelper {
public static Set<String> getLdapGroups(Attributes attrs) throws NamingException {
Set<String> ldapRoles = new HashSet<>();
Attribute groupMembers = attrs.get("memberOf");
if (groupMembers == null)
return ldapRoles;
for (int i = 0; i < groupMembers.size(); i++) {
String memberOfLdapString = attrs.get("memberOf").get(i).toString();
// extract group name from ldap string -> CN=groupname,OU=company,DC=domain,DC=country
LdapName memberOfName = new LdapName(memberOfLdapString);
for (Rdn rdn : memberOfName.getRdns()) {
if (rdn.getType().equalsIgnoreCase("CN")) {
String groupName = rdn.getValue().toString();
ldapRoles.add(groupName);
break;
}
}
}
return ldapRoles;
}
}