Commit ada0273c authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

10: Work with SAML attributes

Task-Url: #10
parent fb0687c7
Pipeline #17635 passed with stage
in 1 minute and 54 seconds
......@@ -3,6 +3,7 @@ package eu.dariah.de.dariahsp.authenticator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import eu.dariah.de.dariahsp.config.model.RoleDefinition;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
......@@ -32,4 +33,25 @@ public abstract class BaseProfileCreator {
}
return roles;
}
protected void mapAndAssignRoles(ExtendedUserProfile profile) {
if (this.canMapRoles(profile)) {
// Determine applicable role definitions
Set<RoleDefinition> mappedDefinitions = this.getMappedRoles(profile);
// Set applicable roles as Strings
profile.setRoles(mappedDefinitions.stream()
.map(mr -> ROLE_PREFIX + mr.getRole().toUpperCase())
.collect(Collectors.toSet()));
// Set maximum applicable level
profile.setLevel(mappedDefinitions.stream()
.mapToInt(RoleDefinition::getLevel)
.max().orElse(0));
}
}
private boolean canMapRoles(ExtendedUserProfile profile) {
return clientName!=null &&
profile.getExternalRoles()!=null && !profile.getExternalRoles().isEmpty() &&
roleDefinitions!=null && !roleDefinitions.isEmpty();
}
}
package eu.dariah.de.dariahsp.authenticator;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.core.profile.creator.ProfileCreator;
import eu.dariah.de.dariahsp.config.model.RoleDefinition;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
import lombok.Data;
import lombok.EqualsAndHashCode;
......@@ -27,26 +23,7 @@ public class LocalProfileCreator extends BaseProfileCreator implements ProfileCr
return Optional.empty();
}
ExtendedUserProfile profile = new ExtendedUserProfile(credentials.getUserProfile());
Set<RoleDefinition> mappedDefinitions;
if (this.canMapRoles(profile)) {
// Determine applicable role definitions
mappedDefinitions = this.getMappedRoles(profile);
// Set applicable roles as Strings
profile.setRoles(mappedDefinitions.stream()
.map(mr -> ROLE_PREFIX + mr.getRole().toUpperCase())
.collect(Collectors.toSet()));
// Set maximum applicable level
profile.setLevel(mappedDefinitions.stream()
.mapToInt(RoleDefinition::getLevel)
.max().orElse(0));
}
return Optional.ofNullable(profile);
}
private boolean canMapRoles(ExtendedUserProfile profile) {
return clientName!=null &&
profile.getExternalRoles()!=null && !profile.getExternalRoles().isEmpty() &&
roleDefinitions!=null && !roleDefinitions.isEmpty();
this.mapAndAssignRoles(profile);
return Optional.ofNullable(profile);
}
}
package eu.dariah.de.dariahsp.authenticator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.core.profile.creator.ProfileCreator;
import org.pac4j.saml.credentials.SAML2Credentials;
import eu.dariah.de.dariahsp.config.model.RoleDefinition;
import eu.dariah.de.dariahsp.config.Attribute;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
@EqualsAndHashCode(callSuper=false)
public class SamlProfileCreator extends BaseProfileCreator implements ProfileCreator<SAML2Credentials> {
public static final String EXTERNAL_ROLES_MAPPED_NAME = "externalRoles";
private final SecurityConfig securityConfig;
public SamlProfileCreator(String clientName) {
public SamlProfileCreator(SecurityConfig securityConfig, String clientName) {
super(clientName);
this.securityConfig = securityConfig;
}
@Override
......@@ -29,26 +33,60 @@ public class SamlProfileCreator extends BaseProfileCreator implements ProfileCre
return Optional.empty();
}
ExtendedUserProfile profile = new ExtendedUserProfile(credentials.getUserProfile());
Set<RoleDefinition> mappedDefinitions;
if (this.canMapRoles(profile)) {
// Determine applicable role definitions
mappedDefinitions = this.getMappedRoles(profile);
// Set applicable roles as Strings
profile.setRoles(mappedDefinitions.stream()
.map(mr -> ROLE_PREFIX + mr.getRole().toUpperCase())
.collect(Collectors.toSet()));
// Set maximum applicable level
profile.setLevel(mappedDefinitions.stream()
.mapToInt(RoleDefinition::getLevel)
.max().orElse(0));
this.assignExternalRoles(profile);
this.assignAttributes(profile);
this.mapAndAssignRoles(profile);
return Optional.ofNullable(profile);
}
private void assignExternalRoles(ExtendedUserProfile profile) {
Attribute externalRolesAttribute = this.getExternalRolesAttribute();
if (externalRolesAttribute==null || !profile.containsAttribute(externalRolesAttribute.getName()) || profile.getAttribute(externalRolesAttribute.getName())==null) {
return;
}
return Optional.ofNullable(profile);
try {
@SuppressWarnings("unchecked")
List<String> memberOfs = (List<String>)profile.getAttribute(externalRolesAttribute.getName());
if (memberOfs.isEmpty()) {
return;
}
if (profile.getExternalRoles()!=null) {
for (String memberOf : memberOfs) {
if (!profile.getExternalRoles().contains(memberOf)) {
profile.getExternalRoles().add(memberOf);
}
}
} else {
profile.setExternalRoles(new LinkedHashSet<>(memberOfs));
}
} catch (Exception e) {
log.warn("Unable to map memberOf attribute to external roles of the profile", e);
}
}
private boolean canMapRoles(ExtendedUserProfile profile) {
return clientName!=null &&
profile.getExternalRoles()!=null && !profile.getExternalRoles().isEmpty() &&
roleDefinitions!=null && !roleDefinitions.isEmpty();
private void assignAttributes(ExtendedUserProfile profile) {
List<Attribute> mappableAttributes = securityConfig.getSaml().getSp().getMappedProfileAttributes();
if (profile.getAttributes().isEmpty()) {
return;
}
for (Attribute a : mappableAttributes) {
if (a.getMappedAttribute().equals("id") && profile.containsAttribute(a.getName())) {
List values = (List)profile.getAttribute(a.getName());
if (!values.isEmpty()) {
profile.setId(values.get(0).toString());
}
}
if (profile.containsAttribute(a.getName())) {
profile.addAttribute(a.getMappedAttribute(), profile.getAttribute(a.getName()));
}
}
}
private Attribute getExternalRolesAttribute() {
return securityConfig.getSaml().getSp().getMappedProfileAttributes().stream()
.filter(a -> a.getMappedAttribute().equals(EXTERNAL_ROLES_MAPPED_NAME))
.findFirst().orElse(null);
}
}
package eu.dariah.de.dariahsp.config;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
public class AttributeConfig implements InitializingBean {
@Autowired private SecurityConfig securityConfig;
@Override
public void afterPropertiesSet() throws Exception {
if (securityConfig.getSaml()==null || !securityConfig.getSaml().isEnabled()) {
}
securityConfig.getSaml().getSp().getAttributeConfig();
}
}
package eu.dariah.de.dariahsp.config;
import org.apache.http.client.HttpClient;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.saml.util.SAML2HttpClientBuilder;
import eu.dariah.de.dariahsp.config.model.SamlSpConfigProperties;
import lombok.Getter;
import lombok.Setter;
......@@ -17,7 +10,7 @@ public class SamlProperties {
private String authorizerName = "saml";
private final KeystoreProperties keystore = new KeystoreProperties();
private final MetadataProperties metadata = new MetadataProperties();
private final SpProperties sp = new SpProperties();
private final SamlSpConfigProperties sp = new SamlSpConfigProperties();
@Getter @Setter
public class KeystoreProperties {
......@@ -30,46 +23,5 @@ public class SamlProperties {
@Getter @Setter
public class MetadataProperties {
private String url;
}
@Getter @Setter
public class SpProperties {
private String metadataResource;
private boolean generateIfNotExists;
private int maxAuthAge = 3600;
private String entityId;
private int httpClientTimoutMs = 2000;
private boolean signMetadata;
private List<String> signingMethods;
private List<String> digestMethods;
private List<String> supportedProtocols;
private boolean authnRequestSigned = true;
private boolean logoutRequestSigned = false;
private boolean wantsAssertionsSigned = true;
private boolean wantsResponsesSigned = true;
private List<ConditionalAttributeSet> attributeConfig;
// ------------------------------------------
// Custom getters for complex default values
// ------------------------------------------
public List<String> getSupportedProtocols() {
List<String> p = supportedProtocols;
if (p==null) {
p = new ArrayList<>();
p.add(SAMLConstants.SAML20P_NS);
}
return p;
}
public int getMaxAuthAge() {
return maxAuthAge<=0 ? 86400 : maxAuthAge; // One day
}
public HttpClient getHttpClient() {
SAML2HttpClientBuilder httpClient = new SAML2HttpClientBuilder();
httpClient.setConnectionTimeout(Duration.ofSeconds(httpClientTimoutMs));
httpClient.setSocketTimeout(Duration.ofSeconds(httpClientTimoutMs));
return httpClient.build();
}
}
}
}
\ No newline at end of file
......@@ -8,20 +8,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.servlet.ServletContext;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.pac4j.http.client.indirect.FormClient;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
import org.pac4j.saml.credentials.SAML2Credentials;
import org.pac4j.springframework.annotation.AnnotationConfig;
import org.pac4j.springframework.component.ComponentConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
......@@ -62,9 +56,6 @@ public class SecurityConfig {
public String getDefaultLoginUrl() { return defaultLoginUrl==null ? baseUrl : defaultLoginUrl; }
public String getDefaultLogoutUrl() { return defaultLogoutUrl==null ? baseUrl : defaultLogoutUrl; }
@Bean AttributeConfig attributeConfig() {
return new AttributeConfig();
}
@Bean
public Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator() {
......@@ -183,7 +174,7 @@ public class SecurityConfig {
private SamlProfileCreator saml2ProfileCreator() {
SamlProfileCreator saml2ProfileCreator = new SamlProfileCreator(saml.getAuthorizerName());
SamlProfileCreator saml2ProfileCreator = new SamlProfileCreator(this, saml.getAuthorizerName());
saml2ProfileCreator.setRoleDefinitions(roleDefinitions);
return saml2ProfileCreator;
}
......
package eu.dariah.de.dariahsp.config.model;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.http.client.HttpClient;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.saml.util.SAML2HttpClientBuilder;
import eu.dariah.de.dariahsp.config.Attribute;
import eu.dariah.de.dariahsp.config.ConditionalAttributeGroup;
import eu.dariah.de.dariahsp.config.ConditionalAttributeSet;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SamlSpConfigProperties {
private String metadataResource;
private boolean generateIfNotExists;
private int maxAuthAge = 3600;
private String entityId;
private int httpClientTimoutMs = 2000;
private boolean signMetadata;
private List<String> signingMethods;
private List<String> digestMethods;
private List<String> supportedProtocols;
private boolean authnRequestSigned = true;
private boolean logoutRequestSigned = false;
private boolean wantsAssertionsSigned = true;
private boolean wantsResponsesSigned = true;
private List<ConditionalAttributeSet> attributeConfig;
private List<Attribute> mappedProfileAttributes = null;
// ------------------------------------------
// Custom getters for complex default values
// ------------------------------------------
public List<String> getSupportedProtocols() {
List<String> p = supportedProtocols;
if (p==null) {
p = new ArrayList<>();
p.add(SAMLConstants.SAML20P_NS);
}
return p;
}
public int getMaxAuthAge() {
return maxAuthAge<=0 ? 86400 : maxAuthAge; // One day
}
public HttpClient getHttpClient() {
SAML2HttpClientBuilder httpClient = new SAML2HttpClientBuilder();
httpClient.setConnectionTimeout(Duration.ofSeconds(httpClientTimoutMs));
httpClient.setSocketTimeout(Duration.ofSeconds(httpClientTimoutMs));
return httpClient.build();
}
public List<Attribute> getMappedProfileAttributes() {
if (mappedProfileAttributes!=null) {
return mappedProfileAttributes;
}
mappedProfileAttributes = new ArrayList<>();
if (attributeConfig!=null) {
for (ConditionalAttributeSet set : attributeConfig) {
mappedProfileAttributes.addAll(this.getAttributesFromSet(set).stream()
.filter(a -> a.getMappedAttribute()!=null)
.collect(Collectors.toList()));
}
}
return mappedProfileAttributes;
}
private List<Attribute> getAttributesFromSet(ConditionalAttributeSet set) {
List<Attribute> mappedAttributes = new ArrayList<>();
if (set.getAttributeGroup()!=null) {
for (ConditionalAttributeGroup group : set.getAttributeGroup()) {
mappedAttributes.addAll(getAttributesFromGroup(group));
}
}
return mappedAttributes;
}
private List<Attribute> getAttributesFromGroup(ConditionalAttributeGroup group) {
List<Attribute> attributes = new ArrayList<>();
if (group.getAttributes()!=null) {
for (Attribute a : group.getAttributes()) {
attributes.add(a);
}
}
return attributes;
}
}
......@@ -11,17 +11,17 @@ auth:
level: 100
mappings:
local: ["application_admin"]
saml2: ["application_admin"]
saml: ["application_admin"]
- role: CONTRIBUTOR
level: 50
mappings:
local: ["application_contributor"]
saml2: ["application_contributor"]
saml: ["application_contributor"]
- role: USER
level: 10
mappings:
local: ["application_user"]
saml2: ["application_user"]
saml: ["application_user"]
local:
enabled: true
authorizerName: local
......@@ -60,14 +60,6 @@ auth:
wantsResponsesSigned: false
httpClientTimoutMs: 2000
attributeConfig:
- stage: ATTRIBUTES
required: true
attributeGroup:
- check: AND
attributes:
- friendlyName: mail
name: urn:oid:0.9.2342.19200300.100.1.3
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- stage: ATTRIBUTES
required: true
attributeGroup:
......@@ -96,10 +88,11 @@ auth:
- check: OPTIONAL
attributes:
- friendlyName: mail
mappedAttribute: email
name: urn:oid:0.9.2342.19200300.100.1.3
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- friendlyName: displayName
mappedAttribute: username
mappedAttribute: display_name
name: urn:oid:2.16.840.1.113730.3.1.241
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- friendlyName: isMemberOf
......
......@@ -23,17 +23,17 @@ auth:
level: 100
mappings:
local: ["application_admin"]
saml2: ["application_admin"]
saml: ["generic-search-admins"]
- role: CONTRIBUTOR
level: 50
mappings:
local: ["application_contributor"]
saml2: ["application_contributor"]
saml: ["generic-search-contributors"]
- role: USER
level: 10
mappings:
local: ["application_user"]
saml2: ["application_user"]
saml: ["application_user"]
local:
enabled: true
authorizerName: local
......@@ -50,7 +50,7 @@ auth:
roles: ["application_user"]
saml:
enabled: true
authorizerName: SAML2Client
authorizerName: saml
keystore:
path: /data/_srv/dariahsp/c105-229.cloud.gwdg.de.jks
pass: clariah
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment