Commit 64614fae authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

10: Work with SAML attributes

Task-Url: #10
parent ada0273c
Pipeline #17771 passed with stage
in 1 minute and 56 seconds
......@@ -4,5 +4,7 @@ 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";
}
......@@ -7,6 +7,9 @@ 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 org.pac4j.saml.credentials.authenticator.SAML2Authenticator;
import eu.dariah.de.dariahsp.Constants;
import eu.dariah.de.dariahsp.config.Attribute;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
......@@ -34,19 +37,29 @@ public class SamlProfileCreator extends BaseProfileCreator implements ProfileCre
}
ExtendedUserProfile profile = new ExtendedUserProfile(credentials.getUserProfile());
this.assignExternalRoles(profile);
this.assignAttributes(profile);
this.setIdPersistenceInfo(profile);
this.mapAndAssignRoles(profile);
return Optional.ofNullable(profile);
return Optional.ofNullable(profile);
}
private void setIdPersistenceInfo(ExtendedUserProfile profile) {
boolean transientId = true;
try {
if (profile.containsAttribute(SAML2Authenticator.SAML_NAME_ID_FORMAT)) {
transientId = profile.getAttribute(SAML2Authenticator.SAML_NAME_ID_FORMAT, String.class).equals(Constants.URN_SAML2_NAMEID_TRANSIENT);
}
} catch (Exception e) {
log.error("Failed to detect and process nameId format", e);
}
//for ()
}
private void assignExternalRoles(ExtendedUserProfile profile) {
Attribute externalRolesAttribute = this.getExternalRolesAttribute();
if (externalRolesAttribute==null || !profile.containsAttribute(externalRolesAttribute.getName()) || profile.getAttribute(externalRolesAttribute.getName())==null) {
return;
}
try {
@SuppressWarnings("unchecked")
List<String> memberOfs = (List<String>)profile.getAttribute(externalRolesAttribute.getName());
List<String> memberOfs = (List<String>)profile.getAttribute(EXTERNAL_ROLES_MAPPED_NAME);
if (memberOfs.isEmpty()) {
return;
}
......@@ -63,30 +76,5 @@ public class SamlProfileCreator extends BaseProfileCreator implements ProfileCre
log.warn("Unable to map memberOf attribute to external roles of the profile", e);
}
}
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);
}
}
......@@ -31,6 +31,7 @@ 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.saml.SAML2RequiredAttributeAuthenticator;
import eu.dariah.de.dariahsp.web.AuthInfoHelper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
......@@ -101,24 +102,22 @@ public class SecurityConfig {
@SuppressWarnings("rawtypes")
public Config config() throws URISyntaxException {
List<Client> clients = new ArrayList<>();
Optional<SAML2Client> samlClient = getSamlClient();
Optional<FormClient> formClient = getFormClient();
SAML2Client samlClient = getSamlClient();
FormClient formClient = getFormClient();
if (samlClient.isPresent()) {
samlClient.get().setProfileCreator(saml2ProfileCreator());
clients.add(samlClient.get());
if (samlClient!=null) {
clients.add(samlClient);
}
if (formClient.isPresent()) {
formClient.get().setProfileCreator(localProfileCreator());
clients.add(formClient.get());
if (formClient!=null) {
clients.add(formClient);
}
return new Config(new Clients(baseUrl().getAbsoluteUrl("/callback"), clients));
}
private Optional<SAML2Client> getSamlClient() throws URISyntaxException {
private SAML2Client getSamlClient() throws URISyntaxException {
if (!saml.isEnabled()) {
return Optional.empty();
return null;
}
final SAML2Configuration cfg = new SAML2Configuration();
......@@ -155,24 +154,27 @@ public class SecurityConfig {
cfg.setSignMetadata(saml.getSp().isSignMetadata());
cfg.setSupportedProtocols(saml.getSp().getSupportedProtocols());
cfg.setHttpClient(saml.getSp().getHttpClient());
cfg.setMappedAttributes(saml.getSp().getMappedAttributesNameMap());
SAML2Client c = new SAML2Client(cfg);
c.setName(saml.getAuthorizerName());
return Optional.of(c);
c.setProfileCreator(saml2ProfileCreator());
c.setAuthenticator(new SAML2RequiredAttributeAuthenticator(cfg.getAttributeAsId(), cfg.getMappedAttributes(), saml.getSp()));
return c;
}
private Optional<FormClient> getFormClient() throws URISyntaxException {
private FormClient getFormClient() throws URISyntaxException {
Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator = localUsernamePasswordAuthenticator();
if (localUsernamePasswordAuthenticator.isPresent()) {
FormClient c = new FormClient(baseUrl().getAbsoluteUrl("/login"), localUsernamePasswordAuthenticator.get());
c.setName(local.getAuthorizerName());
return Optional.of(c);
c.setProfileCreator(localProfileCreator());
return c;
}
return Optional.empty();
return null;
}
private SamlProfileCreator saml2ProfileCreator() {
SamlProfileCreator saml2ProfileCreator = new SamlProfileCreator(this, saml.getAuthorizerName());
saml2ProfileCreator.setRoleDefinitions(roleDefinitions);
......
......@@ -2,7 +2,10 @@ package eu.dariah.de.dariahsp.config.model;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.http.client.HttpClient;
......@@ -33,9 +36,6 @@ public class SamlSpConfigProperties {
private boolean wantsResponsesSigned = true;
private List<ConditionalAttributeSet> attributeConfig;
private List<Attribute> mappedProfileAttributes = null;
// ------------------------------------------
// Custom getters for complex default values
// ------------------------------------------
......@@ -57,19 +57,30 @@ public class SamlSpConfigProperties {
return httpClient.build();
}
public List<Attribute> getMappedProfileAttributes() {
if (mappedProfileAttributes!=null) {
return mappedProfileAttributes;
public Attribute getAttributeByMappedName(String mappedName) {
if (attributeConfig!=null) {
return attributeConfig.stream()
.map(ConditionalAttributeSet::getAttributeGroup)
.flatMap(Collection::stream)
.map(ConditionalAttributeGroup::getAttributes)
.flatMap(Collection::stream)
.filter(a -> a.getMappedAttribute().equals(mappedName))
.findFirst()
.orElse(null);
}
mappedProfileAttributes = new ArrayList<>();
return null;
}
public Map<String, String> getMappedAttributesNameMap() {
Map<String, String> result = new HashMap<>();
if (attributeConfig!=null) {
for (ConditionalAttributeSet set : attributeConfig) {
mappedProfileAttributes.addAll(this.getAttributesFromSet(set).stream()
result.putAll(this.getAttributesFromSet(set).stream()
.filter(a -> a.getMappedAttribute()!=null)
.collect(Collectors.toList()));
.collect(Collectors.toMap(Attribute::getName, Attribute::getMappedAttribute)));
}
}
return mappedProfileAttributes;
return result;
}
private List<Attribute> getAttributesFromSet(ConditionalAttributeSet set) {
......
package eu.dariah.de.dariahsp.local;
/*package eu.dariah.de.dariahsp.sample.local;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import eu.dariah.de.dariahsp.DebugAwareAuthenticationProvider;
import eu.dariah.de.dariahsp.model.User;
import eu.dariah.de.dariahsp.model.UserImpl;
import eu.dariah.de.dariahsp.service.UserService;
public class LocalAuthenticationProvider implements DebugAwareAuthenticationProvider, InitializingBean {
private UserService userService;
private UserDetailsService localUserDb;
private PasswordEncoder encoder;
private String authDebugUser;
private Authentication authDebug;
public UserService getUserService() { return userService; }
public void setUserService(UserService userService) { this.userService = userService; }
public UserDetailsService getLocalUserDb() { return localUserDb; }
public void setLocalUserDb(UserDetailsService localUserDb) { this.localUserDb = localUserDb; }
public PasswordEncoder getEncoder() { return encoder; }
public void setEncoder(PasswordEncoder encoder) { this.encoder = encoder; }
public String isAuthDebugUser() { return authDebugUser; }
public void setAuthDebugUser(String authDebugUser) { this.authDebugUser = authDebugUser; }
@Override
public void afterPropertiesSet() throws Exception {
if (authDebugUser!=null && (System.getProperty("saml")==null || !Boolean.parseBoolean(System.getProperty("saml")))) {
UserDetails user = getLocalUserDb().loadUserByUsername(authDebugUser);
if (user==null) {
user = new UserImpl();
((UserImpl)user).setUsername(authDebugUser);
user = userService.getUserDetails(user);
}
UsernamePasswordAuthenticationToken testAuth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword().hashCode(), user.getAuthorities());
testAuth.setDetails(user);
userService.saveUser((User)user);
authDebug = testAuth;
}
}
@Override
public Authentication getAuthentication() {
if (authDebug!=null && (System.getProperty("saml")==null || !Boolean.parseBoolean(System.getProperty("saml")))) {
SecurityContextHolder.getContext().setAuthentication(authDebug);
// TODO: allow for a global logout of the test user
// this does not work as intended as session timeouts lead to a logout as well
//this.authDebug = null;
}
return SecurityContextHolder.getContext().getAuthentication();
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
UserDetails user = getLocalUserDb().loadUserByUsername(authentication.getName());
if (encoder.matches(authentication.getCredentials().toString(), user.getPassword())) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword().hashCode(), user.getAuthorities());
User u = userService.getUserDetails(user);
auth.setDetails(u);
userService.saveUser(u);
return auth;
} else {
throw new BadCredentialsException("Wrong password");
}
} catch (Exception e) {
throw new BadCredentialsException("Provided username and/or password wrong.");
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
}
}*/
......@@ -14,6 +14,7 @@ import lombok.ToString;
@NoArgsConstructor
@EqualsAndHashCode(callSuper=true)
public class ExtendedUserProfile extends CommonProfile {
private boolean transientId;
private Set<String> externalRoles;
private int level;
......
package eu.dariah.de.dariahsp.saml;
import java.util.Map;
import org.pac4j.core.context.WebContext;
import org.pac4j.saml.credentials.SAML2Credentials;
import org.pac4j.saml.credentials.authenticator.SAML2Authenticator;
import eu.dariah.de.dariahsp.config.model.SamlSpConfigProperties;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SAML2RequiredAttributeAuthenticator extends SAML2Authenticator {
private final SamlSpConfigProperties spConfigProperties;
public SAML2RequiredAttributeAuthenticator(String attributeAsId, SamlSpConfigProperties spConfigProperties) {
super(attributeAsId);
this.spConfigProperties = spConfigProperties;
}
public SAML2RequiredAttributeAuthenticator(final String attributeAsId, final Map<String, String> mappedAttributes, SamlSpConfigProperties spConfigProperties) {
super(attributeAsId, mappedAttributes);
this.spConfigProperties = spConfigProperties;
}
@Override
public void validate(final SAML2Credentials credentials, final WebContext context) {
super.validate(credentials, context);
log.debug("Validated profile: {}", credentials.getUserProfile());
// TODO: Perform required attributes checking
}
}
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