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; package eu.dariah.de.dariahsp.sample.config;
import org.apache.http.client.HttpClient;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.saml.util.SAML2HttpClientBuilder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
...@@ -30,8 +33,10 @@ public class SamlProperties { ...@@ -30,8 +33,10 @@ public class SamlProperties {
@Getter @Setter @Getter @Setter
public class SpProperties { public class SpProperties {
private String externalMetadata; private String metadataResource;
private boolean generateIfNotExists;
private int maxAuthAge = 3600; private int maxAuthAge = 3600;
private String baseUrl = "http://localhost:8080";
private String entityId; private String entityId;
private int httpClientTimoutMs = 2000; private int httpClientTimoutMs = 2000;
private boolean signMetadata; private boolean signMetadata;
...@@ -52,10 +57,19 @@ public class SamlProperties { ...@@ -52,10 +57,19 @@ public class SamlProperties {
List<String> p = supportedProtocols; List<String> p = supportedProtocols;
if (p==null) { if (p==null) {
p = new ArrayList<>(); p = new ArrayList<>();
p.add(SAMLConstants.SAML20_NS); p.add(SAMLConstants.SAML20P_NS);
} }
return p; 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; 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.ArrayList;
import java.util.List; 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.authorization.authorizer.RequireAnyRoleAuthorizer;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients; import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config; import org.pac4j.core.config.Config;
import org.pac4j.http.client.indirect.FormClient; import org.pac4j.http.client.indirect.FormClient;
import org.pac4j.saml.client.SAML2Client; import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration; import org.pac4j.saml.config.SAML2Configuration;
import org.pac4j.saml.util.SAML2HttpClientBuilder;
import org.pac4j.springframework.annotation.AnnotationConfig; import org.pac4j.springframework.annotation.AnnotationConfig;
import org.pac4j.springframework.component.ComponentConfig; import org.pac4j.springframework.component.ComponentConfig;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import eu.dariah.de.dariahsp.sample.CustomAuthorizer; import eu.dariah.de.dariahsp.sample.CustomAuthorizer;
import eu.dariah.de.dariahsp.sample.authenticator.LocalUsernamePasswordAuthenticator; import eu.dariah.de.dariahsp.sample.authenticator.LocalUsernamePasswordAuthenticator;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data @Data
@Slf4j
@Configuration @Configuration
@ConfigurationProperties(prefix = "auth") @ConfigurationProperties(prefix = "auth")
@Import({ComponentConfig.class, AnnotationConfig.class}) @Import({ComponentConfig.class, AnnotationConfig.class})
...@@ -36,19 +40,49 @@ public class SecurityConfig { ...@@ -36,19 +40,49 @@ public class SecurityConfig {
private final SamlProperties saml = new SamlProperties(); private final SamlProperties saml = new SamlProperties();
@Bean @Bean
LocalUsernamePasswordAuthenticator localUsernamePasswordAuthenticator() { public Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator() {
if (!local.isEnabled()) {
return Optional.empty();
}
LocalUsernamePasswordAuthenticator localAuthenticator = new LocalUsernamePasswordAuthenticator(); LocalUsernamePasswordAuthenticator localAuthenticator = new LocalUsernamePasswordAuthenticator();
localAuthenticator.setEncoder(new BCryptPasswordEncoder()); localAuthenticator.setEncoder(new BCryptPasswordEncoder());
if (local.isEnabled()) { localAuthenticator.setLocalUserConfigurations(local.getUsers());
localAuthenticator.setLocalUserConfigurations(local.getUsers()); return Optional.of(localAuthenticator);
}
@Bean
public BaseUrl baseUrl() {
return new BaseUrl(saml.getSp().getBaseUrl());
}
@Bean
@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());
} }
return localAuthenticator; 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;
}
@Bean private Optional<SAML2Client> getSamlClient() throws URISyntaxException {
Config config() { if (!saml.isEnabled()) {
return Optional.empty();
}
final SAML2Configuration cfg = new SAML2Configuration(); final SAML2Configuration cfg = new SAML2Configuration();
// Keystore // Keystore
...@@ -61,55 +95,37 @@ public class SecurityConfig { ...@@ -61,55 +95,37 @@ public class SecurityConfig {
cfg.setIdentityProviderMetadataPath(saml.getMetadata().getUrl()); cfg.setIdentityProviderMetadataPath(saml.getMetadata().getUrl());
// SP Metadata // SP Metadata
if (saml.getSp().getExternalMetadata()!=null) { if (saml.getSp().getMetadataResource()!=null) {
cfg.setServiceProviderMetadataPath(saml.getSp().getExternalMetadata()); // metadata is not automatically written to filesystem, but created in-memory
} else { if (!Files.exists(Paths.get(saml.getSp().getMetadataResource()))) {
cfg.setServiceProviderMetadataPath("/tmp/sp_metadata.xml"); log.warn("Configured SP metadata resource does not exist", new FileNotFoundException(saml.getSp().getMetadataResource()));
} } else {
cfg.setServiceProviderMetadataPath(saml.getSp().getMetadataResource());
if (saml.getSp().getMaxAuthAge()<=0) { }
cfg.setMaximumAuthenticationLifetime(Integer.MAX_VALUE);
} else { } 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.setSignatureAlgorithms(saml.getSp().getSigningMethods());
cfg.setSignatureReferenceDigestMethods(saml.getSp().getDigestMethods()); cfg.setSignatureReferenceDigestMethods(saml.getSp().getDigestMethods());
cfg.setServiceProviderEntityId(saml.getSp().getEntityId()); cfg.setServiceProviderEntityId(saml.getSp().getEntityId());
cfg.setSpLogoutRequestSigned(saml.getSp().isLogoutRequestSigned()); cfg.setSpLogoutRequestSigned(saml.getSp().isLogoutRequestSigned());
cfg.setWantsAssertionsSigned(saml.getSp().isWantsAssertionsSigned()); cfg.setWantsAssertionsSigned(saml.getSp().isWantsAssertionsSigned());
cfg.setWantsResponsesSigned(saml.getSp().isWantsResponsesSigned()); cfg.setWantsResponsesSigned(saml.getSp().isWantsResponsesSigned());
cfg.setAuthnRequestSigned(saml.getSp().isAuthnRequestSigned()); cfg.setAuthnRequestSigned(saml.getSp().isAuthnRequestSigned());
cfg.setSignMetadata(saml.getSp().isSignMetadata()); cfg.setSignMetadata(saml.getSp().isSignMetadata());
cfg.setSupportedProtocols(saml.getSp().getSupportedProtocols()); cfg.setSupportedProtocols(saml.getSp().getSupportedProtocols());
cfg.setHttpClient(saml.getSp().getHttpClient());
return Optional.of(new SAML2Client(cfg));
SAML2Client samlClient = new SAML2Client(cfg); }
private Optional<FormClient> getFormClient() {
Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator = localUsernamePasswordAuthenticator();
// HTTP if (localUsernamePasswordAuthenticator.isPresent()) {
final FormClient formClient = new FormClient("/loginForm", localUsernamePasswordAuthenticator()); return Optional.of(new FormClient("/login", localUsernamePasswordAuthenticator.get()));
}
return Optional.empty();
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; 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.core.config.Config;
import org.pac4j.springframework.security.web.CallbackFilter; import org.pac4j.springframework.security.web.CallbackFilter;
import org.pac4j.springframework.security.web.LogoutFilter; import org.pac4j.springframework.security.web.LogoutFilter;
...@@ -17,29 +21,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi ...@@ -17,29 +21,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
@EnableWebSecurity @EnableWebSecurity
public class WebSecurityConfig { 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 @Configuration
@Order(7) @Order(7)
public static class Saml2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { public static class Saml2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
...@@ -49,51 +31,34 @@ public class WebSecurityConfig { ...@@ -49,51 +31,34 @@ public class WebSecurityConfig {
protected void configure(final HttpSecurity http) throws Exception { protected void configure(final HttpSecurity http) throws Exception {
final SecurityFilter filter = new SecurityFilter(config, "Saml2Client"); List<String> enabledClientNames = config.getClients().findAllClients().stream()
.map(Client::getName)
.collect(Collectors.toList());
final SecurityFilter filter = new SecurityFilter(config, enabledClientNames.stream().collect(Collectors.joining(",")));
// TODO: What happens if there is no client? Everything open or 403??
http http
.antMatcher("/saml/**") .requestMatchers()
.authorizeRequests() .antMatchers("/saml/**", "/form/**")
.antMatchers("/saml/admin.html").hasRole("ADMIN") .and()
.antMatchers("/saml/**").authenticated() .authorizeRequests()
.and() .antMatchers("/saml/admin.html").hasRole("ADMIN")
.addFilterBefore(filter, BasicAuthenticationFilter.class) .antMatchers("/saml/**").authenticated()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); .and()
} .addFilterBefore(filter, BasicAuthenticationFilter.class)
} .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
/* @Configuration if (enabledClientNames.get(0).equals("FormClient")) {
@Order(7) http.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"));
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 http
.antMatcher("/form/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
} }
}*/ }
@Configuration @Configuration
@Order(15) @Order(15)
...@@ -119,11 +84,9 @@ public class WebSecurityConfig { ...@@ -119,11 +84,9 @@ public class WebSecurityConfig {
http http
.authorizeRequests() .authorizeRequests().anyRequest().permitAll()
.antMatchers("/cas/**").authenticated()
.anyRequest().permitAll()
.and() .and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "CasClient")) .exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and() .and()
.addFilterBefore(callbackFilter, BasicAuthenticationFilter.class) .addFilterBefore(callbackFilter, BasicAuthenticationFilter.class)
.addFilterBefore(logoutFilter, CallbackFilter.class) .addFilterBefore(logoutFilter, CallbackFilter.class)
......
...@@ -2,6 +2,8 @@ package eu.dariah.de.dariahsp.sample.controller; ...@@ -2,6 +2,8 @@ package eu.dariah.de.dariahsp.sample.controller;
import java.util.Map; import java.util.Map;
import javax.websocket.server.PathParam;
import org.pac4j.core.client.Client; import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config; import org.pac4j.core.config.Config;
import org.pac4j.core.context.JEEContext; import org.pac4j.core.context.JEEContext;
...@@ -19,6 +21,7 @@ import org.springframework.http.ResponseEntity; ...@@ -19,6 +21,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
...@@ -26,7 +29,7 @@ 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.Constants;
import eu.dariah.de.dariahsp.sample.error.SAML2MetadataNotFoundException; import eu.dariah.de.dariahsp.sample.error.SAML2MetadataNotFoundException;
import eu.dariah.de.dariahsp.sample.metadata.MetadataHelper; import eu.dariah.de.dariahsp.sample.metadata.MetadataHelper;
import javassist.NotFoundException; import eu.dariah.de.dariahsp.sample.error.NotFoundException;
@Controller @Controller
public class SampleController { public class SampleController {
...@@ -54,7 +57,7 @@ public class SampleController { ...@@ -54,7 +57,7 @@ public class SampleController {
return protectedIndex(map); return protectedIndex(map);
} }
@RequestMapping("/loginForm") @RequestMapping("/login")
public String loginForm(Map<String, Object> map) { public String loginForm(Map<String, Object> map) {
final FormClient formClient = (FormClient) config.getClients().findClient(Constants.LOCAL_CLIENT_NAME).get(); final FormClient formClient = (FormClient) config.getClients().findClient(Constants.LOCAL_CLIENT_NAME).get();
map.put("callbackUrl", formClient.getCallbackUrl()); map.put("callbackUrl", formClient.getCallbackUrl());
...@@ -76,8 +79,13 @@ public class SampleController { ...@@ -76,8 +79,13 @@ public class SampleController {
return null; return null;
} }
@GetMapping(value = "/metadata", produces = MediaType.APPLICATION_XML_VALUE) @GetMapping(value = {"/metadata", "/metadata/{action}"}, produces = MediaType.APPLICATION_XML_VALUE)
public @ResponseBody String getMetadata(@RequestParam(required=false) boolean generate, @RequestParam(required=false) boolean filesystem) throws Exception { 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; String metadata;
if ((metadataHelper.isFilesystemMetadataAvailable() && !generate) || filesystem) { if ((metadataHelper.isFilesystemMetadataAvailable() && !generate) || filesystem) {
metadata = metadataHelper.getFromFilesystem(); 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; ...@@ -3,7 +3,7 @@ package eu.dariah.de.dariahsp.sample.error;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus; 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 { public class SAML2MetadataNotFoundException extends RuntimeException {
private static final long serialVersionUID = 4211017703190145692L; 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: ...@@ -20,6 +20,7 @@ auth:
passhash: '$2a$10$EeajSQQUepa7H7.g4xQCaeO.hjUwh0yzYCMrfOkWCZGe1IiWaexa6' </