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

9: Finalize SAML related behavior

Task-Url: #9
parent b146719c
Pipeline #17633 passed with stage
in 1 minute and 55 seconds
package eu.dariah.de.dariahsp.authenticator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import eu.dariah.de.dariahsp.config.model.RoleDefinition;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
import lombok.Data;
@Data
public abstract class BaseProfileCreator {
public static final String ROLE_PREFIX = "ROLE_";
protected final String clientName;
protected List<RoleDefinition> roleDefinitions;
protected Set<RoleDefinition> getMappedRoles(ExtendedUserProfile profile) {
Set<RoleDefinition> roles = new LinkedHashSet<>();
for (RoleDefinition rm : roleDefinitions) {
for (String client : rm.getMappings().keySet()) {
if (!client.equals(clientName)) {
continue;
}
for (String extRole : profile.getExternalRoles()) {
if (!extRole.trim().isEmpty() &&
rm.getMappings().get(client).contains(extRole.trim()) &&
!roles.contains(rm)) {
roles.add(rm);
}
}
}
}
return roles;
}
}
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;
@Data
@EqualsAndHashCode(callSuper=false)
public class LocalProfileCreator extends BaseProfileCreator implements ProfileCreator<UsernamePasswordCredentials> {
public LocalProfileCreator(String clientName) {
super(clientName);
}
@Override
public Optional<UserProfile> create(final UsernamePasswordCredentials credentials, final WebContext context) {
if (credentials.getUserProfile()==null) {
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();
}
}
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.credentials.Credentials;
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.model.ExtendedUserProfile;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
public class UserProfileCreator<C extends Credentials> implements ProfileCreator<C> {
private static final String ROLE_PREFIX = "ROLE_";
private final String clientName;
private List<RoleDefinition> roleDefinitions;
@EqualsAndHashCode(callSuper=false)
public class SamlProfileCreator extends BaseProfileCreator implements ProfileCreator<SAML2Credentials> {
public SamlProfileCreator(String clientName) {
super(clientName);
}
@Override
public Optional<UserProfile> create(final C credentials, final WebContext context) {
public Optional<UserProfile> create(final SAML2Credentials credentials, final WebContext context) {
if (credentials.getUserProfile()==null) {
return Optional.empty();
}
ExtendedUserProfile profile = new ExtendedUserProfile(credentials.getUserProfile());
Set<RoleDefinition> mappedDefinitions;
if (this.canMapRoles(profile)) {
// Determine applicable role definitions
......@@ -38,7 +41,7 @@ public class UserProfileCreator<C extends Credentials> implements ProfileCreator
// Set maximum applicable level
profile.setLevel(mappedDefinitions.stream()
.mapToInt(RoleDefinition::getLevel)
.max().orElse(0));
.max().orElse(0));
}
return Optional.ofNullable(profile);
}
......@@ -48,23 +51,4 @@ public class UserProfileCreator<C extends Credentials> implements ProfileCreator
profile.getExternalRoles()!=null && !profile.getExternalRoles().isEmpty() &&
roleDefinitions!=null && !roleDefinitions.isEmpty();
}
private Set<RoleDefinition> getMappedRoles(ExtendedUserProfile profile) {
Set<RoleDefinition> roles = new LinkedHashSet<>();
for (RoleDefinition rm : roleDefinitions) {
for (String client : rm.getMappings().keySet()) {
if (!client.equals(clientName)) {
continue;
}
for (String extRole : profile.getExternalRoles()) {
if (!extRole.trim().isEmpty() &&
rm.getMappings().get(client).contains(extRole.trim()) &&
!roles.contains(rm)) {
roles.add(rm);
}
}
}
}
return roles;
}
}
......@@ -8,5 +8,6 @@ public class Attribute {
private String name;
private String nameFormat;
private String friendlyName;
private String mappedAttribute;
private String value;
}
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();
}
}
......@@ -47,7 +47,7 @@ public class SamlProperties {
private boolean logoutRequestSigned = false;
private boolean wantsAssertionsSigned = true;
private boolean wantsResponsesSigned = true;
private List<ConditionalAttributeSet> requiredAttributes;
private List<ConditionalAttributeSet> attributeConfig;
// ------------------------------------------
......@@ -62,7 +62,7 @@ public class SamlProperties {
return p;
}
public int getMaxAuthAge() {
return maxAuthAge<=0 ? Integer.MAX_VALUE : maxAuthAge;
return maxAuthAge<=0 ? 86400 : maxAuthAge; // One day
}
public HttpClient getHttpClient() {
SAML2HttpClientBuilder httpClient = new SAML2HttpClientBuilder();
......
......@@ -33,7 +33,8 @@ import org.springframework.security.access.vote.RoleHierarchyVoter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import eu.dariah.de.dariahsp.authenticator.LocalUsernamePasswordAuthenticator;
import eu.dariah.de.dariahsp.authenticator.UserProfileCreator;
import eu.dariah.de.dariahsp.authenticator.SamlProfileCreator;
import eu.dariah.de.dariahsp.authenticator.LocalProfileCreator;
import eu.dariah.de.dariahsp.config.model.RoleDefinition;
import eu.dariah.de.dariahsp.metadata.MetadataHelper;
import eu.dariah.de.dariahsp.web.AuthInfoHelper;
......@@ -54,9 +55,17 @@ public class SecurityConfig {
private String roleHierarchy;
private final List<RoleDefinition> roleDefinitions;
private String baseUrl = "http://localhost:8080";
private String defaultLoginUrl = baseUrl;
private String defaultLogoutUrl = baseUrl;
private String defaultLoginUrl = null;
private String defaultLogoutUrl = null;
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() {
if (!local.isEnabled()) {
......@@ -173,14 +182,14 @@ public class SecurityConfig {
}
private UserProfileCreator<SAML2Credentials> saml2ProfileCreator() {
UserProfileCreator<SAML2Credentials> saml2ProfileCreator = new UserProfileCreator<>(saml.getAuthorizerName());
private SamlProfileCreator saml2ProfileCreator() {
SamlProfileCreator saml2ProfileCreator = new SamlProfileCreator(saml.getAuthorizerName());
saml2ProfileCreator.setRoleDefinitions(roleDefinitions);
return saml2ProfileCreator;
}
private UserProfileCreator<UsernamePasswordCredentials> localProfileCreator() {
UserProfileCreator<UsernamePasswordCredentials> localProfileCreator = new UserProfileCreator<>(local.getAuthorizerName());
private LocalProfileCreator localProfileCreator() {
LocalProfileCreator localProfileCreator = new LocalProfileCreator(local.getAuthorizerName());
localProfileCreator.setRoleDefinitions(roleDefinitions);
return localProfileCreator;
}
......
......@@ -13,4 +13,5 @@ public class AuthPojo {
private String displayName;
private String language;
private Set<String> roles;
private Set<String> externalRoles;
}
......@@ -54,6 +54,7 @@ public class AuthInfoHelper {
pojo.setLevel(profile.getLevel());
pojo.setUserId(profile.getId());
pojo.setSessionId(this.getOrCreateSessionId());
pojo.setExternalRoles(profile.getExternalRoles());
}
return pojo;
}
......
# Config options of the dariahsp core library
# Commented properties reflect default values
auth:
#baseUrl: ${baseUrl}
#baseUrl: http://localhost:8080
#defaultLoginUrl: ${auth.baseUrl}
#defaultLogoutUrl: ${auth.baseUrl}
salt: Qmwp4CO7LDkOUDouAcCcUqd9ZGNbRG5Jyr5lpntOuB9
......@@ -24,6 +24,7 @@ auth:
saml2: ["application_user"]
local:
enabled: true
authorizerName: local
# Same password for each user: 1234
users:
- username: 'admin'
......@@ -37,6 +38,7 @@ auth:
roles: ["application_user"]
saml:
enabled: false
authorizerName: saml
keystore:
path: /data/_srv/dariahsp/c105-229.cloud.gwdg.de.jks
pass: clariah
......@@ -47,17 +49,17 @@ auth:
sp:
#metadataResource: /data/_srv/dariahsp/sp_metadata.xml
maxAuthAge: -1
#entityId: ${auth.saml.sp.baseUrl}
#entityId: ${baseUrl}
signMetadata: true
#signingMethods: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
#digestMethods: http://www.w3.org/2001/04/xmlenc#sha256, http://www.w3.org/2001/04/xmlenc#sha512
#supportedProtocols: urn:oasis:names:tc:SAML:2.0:protocol
authnRequestSigned: true
authnRequestSigned: truevv
logoutRequestSigned: true
wantsAssertionsSigned: true
wantsResponsesSigned: false
httpClientTimoutMs: 2000
requiredAttributes:
attributeConfig:
- stage: ATTRIBUTES
required: true
attributeGroup:
......@@ -85,6 +87,7 @@ auth:
- 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
......@@ -96,5 +99,10 @@ auth:
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
contextPath: /dme
baseUrl: http://localhost:8080${contextPath:/}
#baseUrl: http://localhost:8080${contextPath:/}
baseUrl: https://c105-229.cloud.gwdg.de${contextPath:/}
spring:
mvc:
......@@ -35,6 +36,7 @@ auth:
saml2: ["application_user"]
local:
enabled: true
authorizerName: local
# Same password for each user: 1234
users:
- username: 'admin'
......@@ -47,7 +49,8 @@ auth:
passhash: '$2y$10$nmTcpRxs.RFUstkJJms6U.AW61Jmr64s9VNQLuhpU8gYrgzCapwka'
roles: ["application_user"]
saml:
enabled: false
enabled: true
authorizerName: SAML2Client
keystore:
path: /data/_srv/dariahsp/c105-229.cloud.gwdg.de.jks
pass: clariah
......@@ -58,7 +61,7 @@ auth:
sp:
#metadataResource: /data/_srv/dariahsp/sp_metadata.xml
maxAuthAge: -1
#entityId: ${auth.saml.sp.baseUrl}
entityId: ${baseUrl}
signMetadata: true
#signingMethods: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
#digestMethods: http://www.w3.org/2001/04/xmlenc#sha256, http://www.w3.org/2001/04/xmlenc#sha512
......@@ -68,7 +71,7 @@ auth:
wantsAssertionsSigned: true
wantsResponsesSigned: false
httpClientTimoutMs: 2000
requiredAttributes:
attributeConfig:
- stage: ATTRIBUTES
required: true
attributeGroup:
......@@ -96,6 +99,7 @@ auth:
- 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
......@@ -107,5 +111,10 @@ auth:
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
......@@ -33,12 +33,15 @@
<a href="<s:url value="/protected/contributor" />">Protected url: CONTRIBUTOR role or higher required (security config)</a><br />
<a href="<s:url value="/protected/admin" />">Protected url: ADMINISTRATOR role required</a><br />
<a href="<s:url value="/blocked/noaccess" />">Blocked url: no access allowed</a><br />
<br />
<h2>SAML Metadata</h2>
<a href="<s:url value="/saml/metadata" />">SAML Metadata - best option: configured file before generation</a><br />
<a href="<s:url value="/saml/metadata/generate" />">SAML Metadata - generate based on configuration</a><br />
<a href="<s:url value="/saml/metadata/filesystem" />">SAML Metadata - force load from filesystem (fails if not existing)</a><br />
<h2>Login/Logout</h2>
<a href="<s:url value="/logout?url=/?forcepostlogouturl" />">logout</a><br />
<a href="<s:url value="/logout?url=/?forcepostlogouturl" />">pac4j local logout</a><br />
<a href="<s:url value="/centralLogout?url=/?forcepostlogouturlafteridp" />">pac4j central local logout</a>
<a href="<s:url value="/logout" />">local logout</a><br />
<a href="<s:url value="/centralLogout" />">central logout</a>
<br />
<a href="<s:url value="/forceLogin?client_name=saml" />">Force SAML login</a> (even if already authenticated)<br />
<a href="<s:url value="/forceLogin?client_name=local" />">Force local login</a> (even if already authenticated)<br />
......
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