Commit 5bd2d4f4 authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

1: Rebase on newer OpenSAML

Task-Url: #1
parent 2066778e
Pipeline #17518 passed with stage
in 1 minute and 47 seconds
package eu.dariah.de.dariahsp.sample.config;
import java.net.URI;
import java.net.URISyntaxException;
import lombok.Data;
@Data
public class BaseUrl {
private final String url;
public String getAbsoluteUrl(String relativeComponent) throws URISyntaxException {
String composedUrl = String.format("%s/%s", this.url, relativeComponent);
return new URI(composedUrl).normalize().toString();
}
}
package eu.dariah.de.dariahsp.sample.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 lombok.Getter;
import lombok.Setter;
......@@ -30,8 +33,10 @@ public class SamlProperties {
@Getter @Setter
public class SpProperties {
private String externalMetadata;
private String metadataResource;
private boolean generateIfNotExists;
private int maxAuthAge = 3600;
private String baseUrl = "http://localhost:8080";
private String entityId;
private int httpClientTimoutMs = 2000;
private boolean signMetadata;
......@@ -52,10 +57,19 @@ public class SamlProperties {
List<String> p = supportedProtocols;
if (p==null) {
p = new ArrayList<>();
p.add(SAMLConstants.SAML20_NS);
p.add(SAMLConstants.SAML20P_NS);
}
return p;
}
public int getMaxAuthAge() {
return maxAuthAge<=0 ? Integer.MAX_VALUE : maxAuthAge;
}
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
package eu.dariah.de.dariahsp.sample.config;
import java.time.Duration;
import java.io.FileNotFoundException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
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.context.annotation.Import;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import eu.dariah.de.dariahsp.sample.CustomAuthorizer;
import eu.dariah.de.dariahsp.sample.authenticator.LocalUsernamePasswordAuthenticator;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "auth")
@Import({ComponentConfig.class, AnnotationConfig.class})
......@@ -36,19 +40,49 @@ public class SecurityConfig {
private final SamlProperties saml = new SamlProperties();
@Bean
LocalUsernamePasswordAuthenticator localUsernamePasswordAuthenticator() {
public Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator() {
if (!local.isEnabled()) {
return Optional.empty();
}
LocalUsernamePasswordAuthenticator localAuthenticator = new LocalUsernamePasswordAuthenticator();
localAuthenticator.setEncoder(new BCryptPasswordEncoder());
if (local.isEnabled()) {
localAuthenticator.setLocalUserConfigurations(local.getUsers());
return Optional.of(localAuthenticator);
}
return localAuthenticator;
@Bean
public BaseUrl baseUrl() {
return new BaseUrl(saml.getSp().getBaseUrl());
}
@Bean
Config config() {
@SuppressWarnings("rawtypes")
public Config config() throws URISyntaxException {
List<Client> clients = new ArrayList<>();
Optional<SAML2Client> samlClient = getSamlClient();
Optional<FormClient> formClient = getFormClient();
if (samlClient.isPresent()) {
clients.add(samlClient.get());
}
if (formClient.isPresent()) {
clients.add(formClient.get());
}
final Config config = new Config(new Clients(baseUrl().getAbsoluteUrl("/callback"), clients));
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"));
config.addAuthorizer("custom", new CustomAuthorizer());
//config.addMatcher("excludedPath", new PathMatcher().excludeRegex("^/*$"));
return config;
}
private Optional<SAML2Client> getSamlClient() throws URISyntaxException {
if (!saml.isEnabled()) {
return Optional.empty();
}
final SAML2Configuration cfg = new SAML2Configuration();
// Keystore
......@@ -61,55 +95,37 @@ public class SecurityConfig {
cfg.setIdentityProviderMetadataPath(saml.getMetadata().getUrl());
// SP Metadata
if (saml.getSp().getExternalMetadata()!=null) {
cfg.setServiceProviderMetadataPath(saml.getSp().getExternalMetadata());
if (saml.getSp().getMetadataResource()!=null) {
// metadata is not automatically written to filesystem, but created in-memory
if (!Files.exists(Paths.get(saml.getSp().getMetadataResource()))) {
log.warn("Configured SP metadata resource does not exist", new FileNotFoundException(saml.getSp().getMetadataResource()));
} else {
cfg.setServiceProviderMetadataPath("/tmp/sp_metadata.xml");
cfg.setServiceProviderMetadataPath(saml.getSp().getMetadataResource());
}
if (saml.getSp().getMaxAuthAge()<=0) {
cfg.setMaximumAuthenticationLifetime(Integer.MAX_VALUE);
} else {
cfg.setMaximumAuthenticationLifetime(saml.getSp().getMaxAuthAge());
log.info("SP metadata resource is not configured (auth.saml.sp.metadataResource); metadata will be generated and served in-memory");
}
cfg.setMaximumAuthenticationLifetime(saml.getSp().getMaxAuthAge());
cfg.setSignatureAlgorithms(saml.getSp().getSigningMethods());
cfg.setSignatureReferenceDigestMethods(saml.getSp().getDigestMethods());
cfg.setServiceProviderEntityId(saml.getSp().getEntityId());
cfg.setSpLogoutRequestSigned(saml.getSp().isLogoutRequestSigned());
cfg.setWantsAssertionsSigned(saml.getSp().isWantsAssertionsSigned());
cfg.setWantsResponsesSigned(saml.getSp().isWantsResponsesSigned());
cfg.setAuthnRequestSigned(saml.getSp().isAuthnRequestSigned());
cfg.setSignMetadata(saml.getSp().isSignMetadata());
cfg.setSupportedProtocols(saml.getSp().getSupportedProtocols());
cfg.setHttpClient(saml.getSp().getHttpClient());
return Optional.of(new SAML2Client(cfg));
}
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;
private Optional<FormClient> getFormClient() {
Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator = localUsernamePasswordAuthenticator();
if (localUsernamePasswordAuthenticator.isPresent()) {
return Optional.of(new FormClient("/login", localUsernamePasswordAuthenticator.get()));
}
return Optional.empty();
}
}
package eu.dariah.de.dariahsp.sample.config;
import java.util.List;
import java.util.stream.Collectors;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.springframework.security.web.CallbackFilter;
import org.pac4j.springframework.security.web.LogoutFilter;
......@@ -17,28 +21,6 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
@EnableWebSecurity
public class WebSecurityConfig {
@Configuration
@Order(5)
public static class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private Config config;
protected void configure(final HttpSecurity http) throws Exception {
//final SecurityFilter filter = new SecurityFilter(config, "DirectBasicAuthClient,AnonymousClient");
final SecurityFilter filter = new SecurityFilter(config, "FormClient");
http
.antMatcher("/form/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
@Configuration
@Order(7)
......@@ -49,32 +31,17 @@ public class WebSecurityConfig {
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 {
List<String> enabledClientNames = config.getClients().findAllClients().stream()
.map(Client::getName)
.collect(Collectors.toList());
final SecurityFilter filter = new SecurityFilter(config, "Saml2Client, FormClient");
final SecurityFilter filter = new SecurityFilter(config, enabledClientNames.stream().collect(Collectors.joining(",")));
// TODO: What happens if there is no client? Everything open or 403??
http
.antMatcher("/saml/**")
.requestMatchers()
.antMatchers("/saml/**", "/form/**")
.and()
.authorizeRequests()
.antMatchers("/saml/admin.html").hasRole("ADMIN")
.antMatchers("/saml/**").authenticated()
......@@ -82,17 +49,15 @@ public class WebSecurityConfig {
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
if (enabledClientNames.get(0).equals("FormClient")) {
http.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"));
}
http
.antMatcher("/form/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}*/
}
@Configuration
......@@ -119,11 +84,9 @@ public class WebSecurityConfig {
http
.authorizeRequests()
.antMatchers("/cas/**").authenticated()
.anyRequest().permitAll()
.authorizeRequests().anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "CasClient"))
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(callbackFilter, BasicAuthenticationFilter.class)
.addFilterBefore(logoutFilter, CallbackFilter.class)
......
......@@ -2,6 +2,8 @@ package eu.dariah.de.dariahsp.sample.controller;
import java.util.Map;
import javax.websocket.server.PathParam;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.JEEContext;
......@@ -19,6 +21,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
......@@ -26,7 +29,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import eu.dariah.de.dariahsp.sample.Constants;
import eu.dariah.de.dariahsp.sample.error.SAML2MetadataNotFoundException;
import eu.dariah.de.dariahsp.sample.metadata.MetadataHelper;
import javassist.NotFoundException;
import eu.dariah.de.dariahsp.sample.error.NotFoundException;
@Controller
public class SampleController {
......@@ -54,7 +57,7 @@ public class SampleController {
return protectedIndex(map);
}
@RequestMapping("/loginForm")
@RequestMapping("/login")
public String loginForm(Map<String, Object> map) {
final FormClient formClient = (FormClient) config.getClients().findClient(Constants.LOCAL_CLIENT_NAME).get();
map.put("callbackUrl", formClient.getCallbackUrl());
......@@ -76,8 +79,13 @@ public class SampleController {
return null;
}
@GetMapping(value = "/metadata", produces = MediaType.APPLICATION_XML_VALUE)
public @ResponseBody String getMetadata(@RequestParam(required=false) boolean generate, @RequestParam(required=false) boolean filesystem) throws Exception {
@GetMapping(value = {"/metadata", "/metadata/{action}"}, produces = MediaType.APPLICATION_XML_VALUE)
public @ResponseBody String getMetadata(@PathVariable(required=false) String action) {
if (action!=null && !action.isEmpty() && !action.equals("generate") && !action.equals("filesystem")) {
throw new NotFoundException();
}
boolean generate = action!=null && action.equals("generate");
boolean filesystem = action!=null && action.equals("filesystem");
String metadata;
if ((metadataHelper.isFilesystemMetadataAvailable() && !generate) || filesystem) {
metadata = metadataHelper.getFromFilesystem();
......
package eu.dariah.de.dariahsp.sample.error;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Not found")
public class NotFoundException extends RuntimeException {
private static final long serialVersionUID = 2297069740605303012L;
}
......@@ -3,7 +3,7 @@ package eu.dariah.de.dariahsp.sample.error;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NO_CONTENT, reason="SAML2 metadata not available")
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="SAML2 metadata not available")
public class SAML2MetadataNotFoundException extends RuntimeException {
private static final long serialVersionUID = 4211017703190145692L;
}
package eu.dariah.de.dariahsp.sample.error;
public class SecurityConfigurationException extends Exception {
private static final long serialVersionUID = -7982246481903633882L;
public SecurityConfigurationException() {
super();
}
public SecurityConfigurationException(String message) {
super(message);
}
public SecurityConfigurationException(String message, Throwable cause) {
super(message, cause);
}
public SecurityConfigurationException(Throwable cause) {
super(cause);
}
protected SecurityConfigurationException(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
......@@ -20,6 +20,7 @@ auth:
passhash: '$2a$10$EeajSQQUepa7H7.g4xQCaeO.hjUwh0yzYCMrfOkWCZGe1IiWaexa6'
roles: ["ROLE_CONTRIBUTOR"]
saml:
enabled: true
keystore:
path: /data/_srv/dariahsp/c105-229.cloud.gwdg.de.jks
pass: clariah
......@@ -28,13 +29,14 @@ auth:
metadata:
url: https://aaiproxy.de.dariah.eu/idp/
sp:
#externalMetadata: /etc/dfa/dme/sp_metadata.xml
#metadataResource: /data/_srv/dariahsp/sp_metadata.xml
maxAuthAge: -1
entityId: https://c105-229.cloud.gwdg.de/dme
#baseUrl: https://c105-229.cloud.gwdg.de/dme
entityId: ${auth.saml.sp.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:assertion
#supportedProtocols: urn:oasis:names:tc:SAML:2.0:protocol
authnRequestSigned: true
logoutRequestSigned: true
wantsAssertionsSigned: true
......
......@@ -13,9 +13,11 @@
<appender-ref ref="Console" />
</root>
<!-- LOG "com.baeldung*" at TRACE level -->
<logger name="eu.dariah.de" level="trace" additivity="false">
<appender-ref ref="Console" />
<logger name="eu.dariah.de">
<level value="debug" />
</logger>
<logger name="org.pac4j">
<level value="debug" />
</logger>
</configuration>
\ 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