Commit 250d3274 authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

10: Work with SAML attributes

Task-Url: #10
parent e8e57493
Pipeline #17793 passed with stage
in 1 minute and 57 seconds
package eu.dariah.de.dariahsp;
public class Constants {
public enum AUTHENTICATION_STAGE { AUTHENTICATION, ATTRIBUTES }
public enum REQUIRED_ATTRIBUTE_CHECKLOGIC { AND, OR, OPTIONAL }
public final static String URN_SAML2_NAMEID_TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
......
......@@ -10,4 +10,8 @@ import lombok.Setter;
public class ConditionalAttributeGroup {
private REQUIRED_ATTRIBUTE_CHECKLOGIC check;
private List<Attribute> attributes;
public boolean isRequired() {
return check.equals(REQUIRED_ATTRIBUTE_CHECKLOGIC.AND) || check.equals(REQUIRED_ATTRIBUTE_CHECKLOGIC.OR);
}
}
package eu.dariah.de.dariahsp.config;
import java.util.List;
import eu.dariah.de.dariahsp.Constants.AUTHENTICATION_STAGE;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class ConditionalAttributeSet {
private boolean required;
private AUTHENTICATION_STAGE stage;
private List<ConditionalAttributeGroup> attributeGroup;
}
......@@ -14,7 +14,6 @@ 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;
......@@ -34,7 +33,8 @@ public class SamlSpConfigProperties {
private boolean logoutRequestSigned = false;
private boolean wantsAssertionsSigned = true;
private boolean wantsResponsesSigned = true;
private List<ConditionalAttributeSet> attributeConfig;
private String attributesIncompleteRedirectUrl;
private List<ConditionalAttributeGroup> attributeGroups;
// ------------------------------------------
// Custom getters for complex default values
......@@ -57,11 +57,18 @@ public class SamlSpConfigProperties {
return httpClient.build();
}
public List<ConditionalAttributeGroup> getRequiredAttributeGroups() {
if (attributeGroups!=null) {
return attributeGroups.stream()
.filter(ConditionalAttributeGroup::isRequired)
.collect(Collectors.toList());
}
return new ArrayList<>(0);
}
public Attribute getAttributeByMappedName(String mappedName) {
if (attributeConfig!=null) {
return attributeConfig.stream()
.map(ConditionalAttributeSet::getAttributeGroup)
.flatMap(Collection::stream)
if (attributeGroups!=null) {
return attributeGroups.stream()
.map(ConditionalAttributeGroup::getAttributes)
.flatMap(Collection::stream)
.filter(a -> a.getMappedAttribute().equals(mappedName))
......@@ -73,10 +80,8 @@ public class SamlSpConfigProperties {
public Map<String, String> getMappedAttributesNameMap() {
Map<String, String> result = new HashMap<>();
if (attributeConfig!=null) {
return attributeConfig.stream()
.map(ConditionalAttributeSet::getAttributeGroup)
.flatMap(Collection::stream)
if (attributeGroups!=null) {
return attributeGroups.stream()
.map(ConditionalAttributeGroup::getAttributes)
.flatMap(Collection::stream)
.filter(a -> a.getMappedAttribute()!=null)
......
package eu.dariah.de.dariahsp.error;
public class RequiredAttributesException extends RuntimeException {
private static final long serialVersionUID = 613886651543572109L;
public RequiredAttributesException(final String message) {
super(message);
}
public RequiredAttributesException(final Throwable t) {
super(t);
}
public RequiredAttributesException(String message, Throwable t) {
super(message, t);
}
}
package eu.dariah.de.dariahsp.saml;
import java.util.List;
import java.util.Map;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.CredentialsException;
import org.pac4j.saml.credentials.SAML2Credentials;
import org.pac4j.saml.credentials.SAML2Credentials.SAMLAttribute;
import org.pac4j.saml.credentials.authenticator.SAML2Authenticator;
import eu.dariah.de.dariahsp.Constants.REQUIRED_ATTRIBUTE_CHECKLOGIC;
import eu.dariah.de.dariahsp.config.Attribute;
import eu.dariah.de.dariahsp.config.ConditionalAttributeGroup;
import eu.dariah.de.dariahsp.config.model.SamlSpConfigProperties;
import eu.dariah.de.dariahsp.error.RequiredAttributesException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
......@@ -28,9 +35,51 @@ public class SAML2RequiredAttributeAuthenticator extends SAML2Authenticator {
public void validate(final SAML2Credentials credentials, final WebContext context) {
super.validate(credentials, context);
log.debug("Validated profile: {}", credentials.getUserProfile());
// TODO: Perform required attributes checking
boolean hasAllAttributes = this.hasAllRequiredAttributes(credentials.getAttributes());
if (hasAllAttributes) {
log.debug("Profile validated: {}", credentials.getUserProfile());
} else {
log.warn("Profile misses required attributes: {}", credentials.getUserProfile());
throw new RequiredAttributesException("Profile missing required attributes");
}
}
public boolean hasAllRequiredAttributes(List<SAMLAttribute> providedAttributes) {
List<ConditionalAttributeGroup> reqAttrGroups = spConfigProperties.getRequiredAttributeGroups();
if (reqAttrGroups.isEmpty()) {
return true;
} else if (providedAttributes==null || providedAttributes.isEmpty()) {
return false;
}
for (ConditionalAttributeGroup attrGroup : reqAttrGroups) {
boolean orMatch = false;
if (attrGroup.getAttributes()!=null) {
for (Attribute reqAttr : attrGroup.getAttributes()) {
boolean match = false;
for (SAMLAttribute attr : providedAttributes) {
if ( attr.getNameFormat().equals(reqAttr.getNameFormat()) && attr.getName().equals(reqAttr.getName()) &&
( (reqAttr.getValue()!=null && attr.getAttributeValues().contains(reqAttr.getValue())) ||
reqAttr.getValue()==null) ) {
match = true;
orMatch = true;
break;
}
}
// TODO: Value checks
if (!match && attrGroup.getCheck().equals(REQUIRED_ATTRIBUTE_CHECKLOGIC.AND)) {
return false;
}
if (orMatch && attrGroup.getCheck().equals(REQUIRED_ATTRIBUTE_CHECKLOGIC.OR)) {
break;
}
}
}
if (!orMatch && attrGroup.getCheck().equals(REQUIRED_ATTRIBUTE_CHECKLOGIC.OR)) {
return false;
}
}
return true;
}
}
......@@ -59,43 +59,31 @@ auth:
wantsAssertionsSigned: true
wantsResponsesSigned: false
httpClientTimoutMs: 2000
attributeConfig:
- stage: ATTRIBUTES
required: true
attributeGroup:
- check: OR
attributes:
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
value: Terms_of_Use_v5.pdf
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
value: foobar-service-agreement_version1.pdf
- stage: AUTHENTICATION
required: true
attributeGroup:
- check: AND
attributes:
- friendlyName: eduPersonPrincipalName
mappedAttribute: id
name: urn:oid:1.3.6.1.4.1.5923.1.1.1.6
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- stage: AUTHENTICATION
required: false
attributeGroup:
- 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: 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
mappedAttribute: externalRoles
name: urn:oid:1.3.6.1.4.1.5923.1.5.1.1
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
\ No newline at end of file
attributesIncompleteRedirectUrl: https://auth.de.dariah.eu/cgi-bin/selfservice/ldapportal.pl
attributeGroups:
- 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
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
#value: Terms_of_Use_germ_engl_v6.pdf
- friendlyName: eduPersonPrincipalName
mappedAttribute: id
name: urn:oid:1.3.6.1.4.1.5923.1.1.1.6
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- check: OPTIONAL
attributes:
- friendlyName: mail
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
name: urn:oid:2.16.840.1.113730.3.1.241
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- friendlyName: isMemberOf
mappedAttribute: externalRoles
name: urn:oid:1.3.6.1.4.1.5923.1.5.1.1
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
\ No newline at end of file
......@@ -2,6 +2,7 @@ package eu.dariah.de.dariahsp.sample.controller;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
......@@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.error.RequiredAttributesException;
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
......@@ -31,9 +33,27 @@ public class ErrorController extends BasicErrorController {
@RequestMapping(produces = {"text/html"})
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> attr = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
final HttpStatus status = getStatus(request);
HttpStatus status = getStatus(request);
this.assembleMap(attr, status);
// This is an error that needs special treatment in DARIAH/CLARIAH
Object ex = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (ex!=null && RequiredAttributesException.class.isAssignableFrom(ex.getClass())) {
StringBuilder errorStrBldr = new StringBuilder();
errorStrBldr.append("Your IdP did not provide all required attributes. ");
if (securityConfig.getSaml().getSp().getAttributesIncompleteRedirectUrl()!=null) {
errorStrBldr
.append("Please visit ")
.append("<a href='").append(securityConfig.getSaml().getSp().getAttributesIncompleteRedirectUrl()).append("'>")
.append(securityConfig.getSaml().getSp().getAttributesIncompleteRedirectUrl())
.append("</a> to validate your profile setup.");
}
attr.put("message", errorStrBldr.toString());
attr.put("error", HttpStatus.FORBIDDEN);
}
return new ModelAndView("index", attr);
}
......
......@@ -71,50 +71,31 @@ auth:
wantsAssertionsSigned: true
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:
- check: OR
attributes:
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
value: Terms_of_Use_v5.pdf
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
value: foobar-service-agreement_version1.pdf
- stage: AUTHENTICATION
required: true
attributeGroup:
- check: AND
attributes:
- friendlyName: eduPersonPrincipalName
mappedAttribute: id
name: urn:oid:1.3.6.1.4.1.5923.1.1.1.6
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- stage: AUTHENTICATION
required: false
attributeGroup:
- check: OPTIONAL
attributes:
- friendlyName: mail
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
name: urn:oid:2.16.840.1.113730.3.1.241
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- friendlyName: isMemberOf
mappedAttribute: externalRoles
name: urn:oid:1.3.6.1.4.1.5923.1.5.1.1
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
\ No newline at end of file
attributesIncompleteRedirectUrl: https://auth.de.dariah.eu/cgi-bin/selfservice/ldapportal.pl
attributeGroups:
- 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
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
#value: Terms_of_Use_germ_engl_v6.pdf
- friendlyName: eduPersonPrincipalName
mappedAttribute: id
name: urn:oid:1.3.6.1.4.1.5923.1.1.1.6
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- check: OPTIONAL
attributes:
- friendlyName: mail
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
name: urn:oid:2.16.840.1.113730.3.1.241
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- friendlyName: isMemberOf
mappedAttribute: externalRoles
name: urn:oid:1.3.6.1.4.1.5923.1.5.1.1
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
\ No newline at end of file
Markdown is supported
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