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

2: Migrate core behavior to new base

Task-Url: #2
parent 6904dd1f
Pipeline #17499 passed with stage
in 1 minute and 51 seconds
......@@ -16,7 +16,8 @@ allprojects {
jupiterVersion = "5.7.0"
slf4jVersion = "1.7.30"
logbackVersion = "1.2.3"
coreVersion = "6.0.0-SNAPSHOT"
librarySets = [
commonTest: [
"org.junit.jupiter:junit-jupiter-engine:$jupiterVersion",
......
......@@ -12,7 +12,7 @@ ext {
}
dependencies {
//implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation "de.unibamberg.minf.core:core-metamodel:$coreVersion"
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
compileOnly 'org.projectlombok:lombok'
......
package eu.dariah.de.dariahsp.sample;
public class Constants {
public enum AUTHENTICATION_STAGE { AUTHENTICATION, ATTRIBUTES }
public enum REQUIRED_ATTRIBUTE_CHECKLOGIC { AND, OR, OPTIONAL }
}
package eu.dariah.de.dariahsp.sample.authenticator;
import java.util.ArrayList;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.pac4j.core.credentials.authenticator.Authenticator;
......@@ -7,19 +9,23 @@ import org.pac4j.core.exception.CredentialsException;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.core.util.Pac4jConstants;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import eu.dariah.de.dariahsp.sample.local.LocalUserConf;
import eu.dariah.de.dariahsp.sample.local.LocalUsers;
import eu.dariah.de.dariahsp.sample.model.RoleImpl;
import eu.dariah.de.dariahsp.sample.model.UserImpl;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LocalUsernamePasswordAuthenticator implements Authenticator<UsernamePasswordCredentials> {
@Getter @Setter private LocalUserConf[] localUserConfigurations;
@Getter @Setter private PasswordEncoder encoder;
@Getter @Setter
public class LocalUsernamePasswordAuthenticator implements Authenticator<UsernamePasswordCredentials>, UserDetailsService {
private LocalUsers[] localUserConfigurations;
private PasswordEncoder encoder;
@Override
public void validate(UsernamePasswordCredentials credentials, WebContext context) {
......@@ -35,7 +41,7 @@ public class LocalUsernamePasswordAuthenticator implements Authenticator<Usernam
if (CommonHelper.isBlank(password)) {
throw new CredentialsException("Password cannot be blank");
}
for (LocalUserConf cnf : localUserConfigurations) {
for (LocalUsers cnf : localUserConfigurations) {
if (cnf.getUsername().equals(username)) {
if (encoder.matches(password, cnf.getPasshash())) {
final CommonProfile profile = new CommonProfile();
......@@ -59,4 +65,26 @@ public class LocalUsernamePasswordAuthenticator implements Authenticator<Usernam
throw e;
}
}
@Override
public UserDetails loadUserByUsername(String username) {
if (localUserConfigurations==null) {
throw new UsernameNotFoundException("Username not found: no local users configured");
}
for (LocalUsers uc : localUserConfigurations) {
if (uc.getUsername().equals(username)) {
UserImpl u = new UserImpl();
u.setUsername(uc.getUsername());
u.setHashedPassword(uc.getPasshash());
if (uc.getRoles()!=null && !uc.getRoles().isEmpty()) {
u.setAuthorities(new ArrayList<>(uc.getRoles().size()));
for (String r : uc.getRoles()) {
u.getAuthorities().add(new RoleImpl(r));
}
}
return u;
}
}
throw new UsernameNotFoundException("Username not found: no local users configured");
}
}
package eu.dariah.de.dariahsp.sample.config;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class Attribute {
private String name;
private String nameFormat;
private String friendlyName;
private String value;
}
package eu.dariah.de.dariahsp.sample.config;
import java.util.List;
import eu.dariah.de.dariahsp.sample.Constants.REQUIRED_ATTRIBUTE_CHECKLOGIC;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class ConditionalAttributeGroup {
private REQUIRED_ATTRIBUTE_CHECKLOGIC check;
private List<Attribute> attributes;
}
package eu.dariah.de.dariahsp.sample.config;
import java.util.List;
import eu.dariah.de.dariahsp.sample.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;
}
package eu.dariah.de.dariahsp.sample.config;
import eu.dariah.de.dariahsp.sample.local.LocalUsers;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class LocalSecurityProperties {
private boolean enabled;
private LocalUsers[] users;
}
\ No newline at end of file
package eu.dariah.de.dariahsp.sample.config;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.http.client.indirect.FormClient;
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
import org.pac4j.saml.util.SAML2HttpClientBuilder;
import org.pac4j.springframework.annotation.AnnotationConfig;
import org.pac4j.springframework.component.ComponentConfig;
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.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import eu.dariah.de.dariahsp.sample.CustomAuthorizer;
import eu.dariah.de.dariahsp.sample.authenticator.LocalUsernamePasswordAuthenticator;
import eu.dariah.de.dariahsp.sample.local.LocalUserConf;
import eu.dariah.de.dariahsp.sample.local.LocalUserConfService;
import lombok.Data;
@Data
@Configuration
@ConfigurationProperties(prefix = "saml")
@Import({ComponentConfig.class, AnnotationConfig.class})
public class SamlConfig {
private String keystore;
private String keystorePassword;
private String privateKeyPassword;
private String identityProviderMetadata;
private LocalUserConf[] localUserConfigurations;
@Bean
LocalUserConfService localUserConfService() {
return new LocalUserConfService();
}
@Bean
LocalUsernamePasswordAuthenticator localUsernamePasswordAuthenticator() {
LocalUsernamePasswordAuthenticator localAuthenticator = new LocalUsernamePasswordAuthenticator();
localAuthenticator.setLocalUserConfigurations(localUserConfigurations);
localAuthenticator.setEncoder(new BCryptPasswordEncoder());
return localAuthenticator;
}
@Bean
Config config() {
final SAML2Configuration cfg = new SAML2Configuration(this.getKeystore(),
this.getKeystorePassword(),
this.getPrivateKeyPassword(),
this.getIdentityProviderMetadata());
cfg.setMaximumAuthenticationLifetime(3600);
//cfg.setServiceProviderEntityId("http://localhost:8080/callback?client_name=SAML2Client");
cfg.setServiceProviderMetadataPath("/data/_srv/dariahsp/sp_metadata.xml");
//cfg.setServiceProviderMetadataPath("sp_metadata.xml");
cfg.setAuthnRequestBindingType(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
cfg.setResponseBindingType(SAMLConstants.SAML2_POST_BINDING_URI);
// lifetime in seconds
cfg.setMaximumAuthenticationLifetime(600);
// custom SP entity ID
cfg.setServiceProviderEntityId("https://c105-229.cloud.gwdg.de/dme");
cfg.setWantsAssertionsSigned(true);
cfg.setAuthnRequestSigned(true);
SAML2HttpClientBuilder httpClient = new SAML2HttpClientBuilder();
httpClient.setConnectionTimeout(Duration.ofSeconds(2));
httpClient.setSocketTimeout(Duration.ofSeconds(2));
cfg.setHttpClient(httpClient.build());
// cfg.setForceServiceProviderMetadataGeneration(true);
List<String> supportedProtocols = new ArrayList<>();
supportedProtocols.add(SAMLConstants.SAML20_NS);
cfg.setSupportedProtocols(supportedProtocols);
SAML2Client samlClient = new SAML2Client(cfg);
// HTTP
final FormClient formClient = new FormClient("/loginForm", localUsernamePasswordAuthenticator());
final Config config = new Config(new Clients("/callback", samlClient, formClient));
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"));
config.addAuthorizer("custom", new CustomAuthorizer());
//config.addMatcher("excludedPath", new PathMatcher().excludeRegex("^/*$"));
return config;
}
}
package eu.dariah.de.dariahsp.sample.config;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class SamlProperties {
private boolean enabled = true;
private final KeystoreProperties keystore = new KeystoreProperties();
private final MetadataProperties metadata = new MetadataProperties();
private final SpProperties sp = new SpProperties();
@Getter @Setter
public class KeystoreProperties {
private String path;
private String pass;
private String alias;
private String aliaspass;
}
@Getter @Setter
public class MetadataProperties {
private String url;
}
@Getter @Setter
public class SpProperties {
private String externalMetadata;
private Integer maxAuthAge;
private String entityId;
private boolean signMetadata;
private String signingAlgorithm;
private boolean requireArtifactResolveSigned;
private boolean requireLogoutRequestSigned;
private boolean requireLogoutResponseSigned;
private List<String> allowedNameIds;
private List<ConditionalAttributeSet> requiredAttributes;
}
}
\ No newline at end of file
package eu.dariah.de.dariahsp.sample.config;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.springframework.security.web.CallbackFilter;
import org.pac4j.springframework.security.web.LogoutFilter;
import org.pac4j.springframework.security.web.Pac4jEntryPoint;
import org.pac4j.springframework.security.web.SecurityFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.pac4j.http.client.indirect.FormClient;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
import org.pac4j.saml.util.SAML2HttpClientBuilder;
import org.pac4j.springframework.annotation.AnnotationConfig;
import org.pac4j.springframework.component.ComponentConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfig {
/* @Configuration
@Order(5)
public static class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private Config config;
protected void configure(final HttpSecurity http) throws Exception {
import org.springframework.context.annotation.Import;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//final SecurityFilter filter = new SecurityFilter(config, "DirectBasicAuthClient,AnonymousClient");
final SecurityFilter filter = new SecurityFilter(config, "FormClient");
import eu.dariah.de.dariahsp.sample.CustomAuthorizer;
import eu.dariah.de.dariahsp.sample.authenticator.LocalUsernamePasswordAuthenticator;
import lombok.Data;
http
.antMatcher("/form/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
@Data
@Configuration
@ConfigurationProperties(prefix = "auth")
@Import({ComponentConfig.class, AnnotationConfig.class})
public class SecurityConfig {
@Configuration
@Order(7)
public static class Saml2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private Config config;
protected void configure(final HttpSecurity http) throws Exception {
final SecurityFilter filter = new SecurityFilter(config, "Saml2Client");
http
.antMatcher("/saml/**")
.authorizeRequests()
.antMatchers("/saml/admin.html").hasRole("ADMIN")
.antMatchers("/saml/**").authenticated()
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
*/
@Configuration
@Order(7)
public static class CombinedSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private Config config;
protected void configure(final HttpSecurity http) throws Exception {
final SecurityFilter filter = new SecurityFilter(config, "Saml2Client, FormClient");
http
.antMatcher("/saml/**")
.authorizeRequests()
.antMatchers("/saml/admin.html").hasRole("ADMIN")
.antMatchers("/saml/**").authenticated()
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http
.antMatcher("/form/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
@Configuration
@Order(15)
public static class DefaultWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private Config config;
protected void configure(final HttpSecurity http) throws Exception {
final CallbackFilter callbackFilter = new CallbackFilter(config);
callbackFilter.setMultiProfile(true);
final LogoutFilter logoutFilter = new LogoutFilter(config, "/?defaulturlafterlogout");
logoutFilter.setDestroySession(true);
logoutFilter.setSuffix("/pac4jLogout");
final LogoutFilter centralLogoutFilter = new LogoutFilter(config, "http://localhost:8080/?defaulturlafterlogoutafteridp");
centralLogoutFilter.setLocalLogout(false);
centralLogoutFilter.setCentralLogout(true);
centralLogoutFilter.setLogoutUrlPattern("http://localhost:8080/.*");
centralLogoutFilter.setSuffix("/pac4jCentralLogout");
private String salt;
private final LocalSecurityProperties local = new LocalSecurityProperties();
private final SamlProperties saml = new SamlProperties();
@Bean
LocalUsernamePasswordAuthenticator localUsernamePasswordAuthenticator() {
LocalUsernamePasswordAuthenticator localAuthenticator = new LocalUsernamePasswordAuthenticator();
localAuthenticator.setEncoder(new BCryptPasswordEncoder());
if (local.isEnabled()) {
localAuthenticator.setLocalUserConfigurations(local.getUsers());
}
return localAuthenticator;
}
http
.authorizeRequests()
.antMatchers("/cas/**").authenticated()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "CasClient"))
.and()
.addFilterBefore(callbackFilter, BasicAuthenticationFilter.class)
.addFilterBefore(logoutFilter, CallbackFilter.class)
.addFilterAfter(centralLogoutFilter, CallbackFilter.class)
.csrf().disable()
.logout()
.logoutSuccessUrl("/");
}
}
@Bean
Config config() {
final SAML2Configuration cfg = new SAML2Configuration();
// Keystore
cfg.setKeystoreAlias(saml.getKeystore().getAlias());
cfg.setKeystorePath(saml.getKeystore().getPath());
cfg.setPrivateKeyPassword(saml.getKeystore().getAliaspass());
// IdP Metadata
cfg.setIdentityProviderMetadataPath(saml.getMetadata().getUrl());
// SP Metadata
cfg.setServiceProviderMetadataPath(saml.getSp().getExternalMetadata());
cfg.setMaximumAuthenticationLifetime(saml.getSp().getMaxAuthAge());
cfg.setServiceProviderEntityId(saml.getSp().getEntityId());
cfg.setSpLogoutRequestSigned(saml.getSp().isRequireLogoutRequestSigned());
// TODO: What to do here?
//cfg.setAuthnRequestBindingType(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
//cfg.setResponseBindingType(SAMLConstants.SAML2_POST_BINDING_URI);
// TODO: Refactor old properties
cfg.setWantsAssertionsSigned(saml.getSp().isRequireArtifactResolveSigned());
cfg.setWantsResponsesSigned(saml.getSp().isRequireLogoutResponseSigned());
// TODO: New properties
/*
cfg.setAuthnRequestSigned(true);
cfg.setSignatureAlgorithms(signatureAlgorithms);
cfg.setSignMetadata(true);
SAML2HttpClientBuilder httpClient = new SAML2HttpClientBuilder();
httpClient.setConnectionTimeout(Duration.ofSeconds(2));
httpClient.setSocketTimeout(Duration.ofSeconds(2));
cfg.setHttpClient(httpClient.build());*/
// TODO: Produce or provide metadata in controller
//String spMetadata = samlClient.getServiceProviderMetadataResolver().getMetadata();
// Static: Support only SAML2
List<String> supportedProtocols = new ArrayList<>();
supportedProtocols.add(SAMLConstants.SAML20_NS);
cfg.setSupportedProtocols(supportedProtocols);
SAML2Client samlClient = new SAML2Client(cfg);
// HTTP
final FormClient formClient = new FormClient("/loginForm", localUsernamePasswordAuthenticator());
final Config config = new Config(new Clients("/callback", samlClient, formClient));
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"));
config.addAuthorizer("custom", new CustomAuthorizer());
//config.addMatcher("excludedPath", new PathMatcher().excludeRegex("^/*$"));
return config;
}
}
package eu.dariah.de.dariahsp.sample.config;
import org.pac4j.core.config.Config;
import org.pac4j.springframework.security.web.CallbackFilter;
import org.pac4j.springframework.security.web.LogoutFilter;
import org.pac4j.springframework.security.web.Pac4jEntryPoint;
import org.pac4j.springframework.security.web.SecurityFilter;
import org.springframework.beans.factory.annotation.Autowired;