Commit 034603f7 authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

7: Finalize Spring Boot example app

Task-Url: #7
parent 55ddb6f0
Pipeline #17626 passed with stage
in 1 minute and 58 seconds
package eu.dariah.de.dariahsp;
//import org.apache.commons.lang.StringUtils;
import org.pac4j.core.authorization.authorizer.ProfileAuthorizer;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.profile.CommonProfile;
import java.util.List;
public class CustomAuthorizer extends ProfileAuthorizer<CommonProfile> {
@Override
public boolean isAuthorized(final WebContext context, final List<CommonProfile> profiles) {
return isAnyAuthorized(context, profiles);
}
@Override
public boolean isProfileAuthorized(final WebContext context, final CommonProfile profile) {
if (profile == null) {
return false;
}
//return StringUtils.startsWith(profile.getUsername(), "jle");
return true;
}
}
......@@ -11,8 +11,8 @@ import org.pac4j.core.credentials.Credentials;
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 eu.dariah.de.dariahsp.model.config.RoleDefinition;
import lombok.Data;
@Data
......
......@@ -14,7 +14,7 @@ import lombok.Setter;
@Getter @Setter
public class SamlProperties {
private boolean enabled = true;
private String authorizerName = "saml2";
private String authorizerName = "saml";
private final KeystoreProperties keystore = new KeystoreProperties();
private final MetadataProperties metadata = new MetadataProperties();
private final SpProperties sp = new SpProperties();
......@@ -37,7 +37,6 @@ public class SamlProperties {
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;
......
......@@ -6,11 +6,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
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;
......@@ -28,16 +24,15 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.access.vote.RoleHierarchyVoter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import eu.dariah.de.dariahsp.CustomAuthorizer;
import eu.dariah.de.dariahsp.authenticator.LocalUsernamePasswordAuthenticator;
import eu.dariah.de.dariahsp.authenticator.UserProfileCreator;
import eu.dariah.de.dariahsp.config.model.RoleDefinition;
import eu.dariah.de.dariahsp.metadata.MetadataHelper;
import eu.dariah.de.dariahsp.model.config.RoleDefinition;
import eu.dariah.de.dariahsp.web.AuthInfoHelper;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Data
......@@ -50,9 +45,10 @@ public class SecurityConfig {
private final LocalSecurityProperties local = new LocalSecurityProperties();
private final SamlProperties saml = new SamlProperties();
@Getter private String salt;
@Getter private String roleHierarchy;
@Getter private final List<RoleDefinition> roleDefinitions;
private String salt;
private String roleHierarchy;
private final List<RoleDefinition> roleDefinitions;
private String baseUrl = "http://localhost:8080";
@Bean
public Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator() {
......@@ -72,7 +68,8 @@ public class SecurityConfig {
@Bean
public BaseUrl baseUrl() {
return new BaseUrl(saml.getSp().getBaseUrl());
log.info("Base URL for security: {}", baseUrl);
return new BaseUrl(baseUrl);
}
@Bean
......@@ -81,17 +78,18 @@ public class SecurityConfig {
}
@Bean
public Optional<RoleHierarchy> roleHierarchy() {
if (roleHierarchy==null || roleHierarchy.isEmpty()) {
log.info("RoleHierarchy not configured; no role hierarchy available");
return Optional.empty();
}
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy(roleHierarchy);
log.info("RoleHierarchy configured: {}", roleHierarchy);
return Optional.of(r);
return r;
}
@Bean
public RoleHierarchyVoter roleVoter() {
return new RoleHierarchyVoter(roleHierarchy());
}
@Bean
@SuppressWarnings("rawtypes")
public Config config() throws URISyntaxException {
......@@ -106,14 +104,8 @@ public class SecurityConfig {
if (formClient.isPresent()) {
formClient.get().setProfileCreator(localProfileCreator());
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;
}
return new Config(new Clients(baseUrl().getAbsoluteUrl("/callback"), clients));
}
......
package eu.dariah.de.dariahsp.model.config;
package eu.dariah.de.dariahsp.config.model;
import java.util.Map;
import java.util.Set;
......
package eu.dariah.de.dariahsp.config;
package eu.dariah.de.dariahsp.config.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
......@@ -9,7 +9,7 @@ import eu.dariah.de.dariahsp.web.AuthInfoHelper;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DefaultWebMvcConfigurer implements WebMvcConfigurer {
public class AuthInfoConfigurer implements WebMvcConfigurer {
@Autowired private AuthInfoHelper authInfoHelper;
@Override
......
package eu.dariah.de.dariahsp.config.web;
import java.util.List;
import java.util.stream.Collectors;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import eu.dariah.de.dariahsp.config.BaseUrl;
public abstract class BaseSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired protected Config config;
@Autowired protected BaseUrl baseUrl;
protected List<String> getEnabledClientNames() {
return config.getClients().findAllClients().stream()
.map(Client::getName)
.collect(Collectors.toList());
}
}
package eu.dariah.de.dariahsp.config;
package eu.dariah.de.dariahsp.config.web;
import java.util.List;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
public class DefaultFiltersConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired private Config config;
public class DefaultFiltersConfigurerAdapter extends BaseSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
List<String> enabledClientNames = this.getEnabledClientNames();
final CallbackFilter callbackFilter = new CallbackFilter(config);
callbackFilter.setMultiProfile(true);
final LogoutFilter logoutFilter = new LogoutFilter(config, "/?defaulturlafterlogout");
logoutFilter.setDestroySession(true);
logoutFilter.setSuffix("/pac4jLogout");
logoutFilter.setSuffix("/logout");
final LogoutFilter centralLogoutFilter = new LogoutFilter(config, "http://localhost:8080/?defaulturlafterlogoutafteridp");
final LogoutFilter centralLogoutFilter = new LogoutFilter(config, baseUrl.getAbsoluteUrl("/?defaulturlafterlogoutafteridp"));
centralLogoutFilter.setLocalLogout(false);
centralLogoutFilter.setCentralLogout(true);
centralLogoutFilter.setLogoutUrlPattern("http://localhost:8080/.*");
centralLogoutFilter.setSuffix("/pac4jCentralLogout");
centralLogoutFilter.setLogoutUrlPattern(baseUrl.getAbsoluteUrl("/.*"));
centralLogoutFilter.setSuffix("/centralLogout");
http
.authorizeRequests().anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"))
.and()
.addFilterBefore(callbackFilter, BasicAuthenticationFilter.class)
.addFilterBefore(logoutFilter, CallbackFilter.class)
.addFilterAfter(centralLogoutFilter, CallbackFilter.class)
.csrf().disable()
.logout()
.logoutSuccessUrl("/");
http.authorizeRequests().anyRequest().permitAll();
if (!enabledClientNames.isEmpty()) {
http.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, enabledClientNames.get(0)));
}
http.addFilterBefore(callbackFilter, BasicAuthenticationFilter.class)
.addFilterBefore(logoutFilter, CallbackFilter.class)
.addFilterAfter(centralLogoutFilter, CallbackFilter.class)
.csrf().disable()
.logout().logoutSuccessUrl("/");
}
}
\ No newline at end of file
package eu.dariah.de.dariahsp.config.web;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleHierarchyVoter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true, jsr250Enabled = true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired private RoleHierarchyVoter roleVoter;
@Override
public AffirmativeBased accessDecisionManager() {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
decisionVoters.add(roleVoter);
return new AffirmativeBased(decisionVoters);
}
}
\ No newline at end of file
package eu.dariah.de.dariahsp.config;
package eu.dariah.de.dariahsp.config.web;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.springframework.security.web.Pac4jEntryPoint;
import org.pac4j.springframework.security.web.SecurityFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
public class CombinedSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired private Config config;
@Autowired private Optional<RoleHierarchy> roleHierarchy;
public class SecurityConfigurerAdapter extends BaseSecurityConfigurerAdapter {
@Autowired private RoleHierarchy roleHierarchy;
@Override
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, enabledClientNames.stream().collect(Collectors.joining(",")));
List<String> enabledClientNames = this.getEnabledClientNames();
final SecurityFilter filter = new SecurityFilter(config, enabledClientNames.stream().collect(Collectors.joining(",")));
http
/*.requestMatchers()
......@@ -43,16 +33,14 @@ public class CombinedSecurityConfigurationAdapter extends WebSecurityConfigurerA
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
if (!enabledClientNames.isEmpty() && enabledClientNames.get(0).equals("FormClient")) {
http.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, "FormClient"));
if (!enabledClientNames.isEmpty()) {
http.exceptionHandling().authenticationEntryPoint(new Pac4jEntryPoint(config, enabledClientNames.get(0)));
}
}
protected SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
protected SecurityExpressionHandler<FilterInvocation> hierarchicalExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
if (roleHierarchy.isPresent()) {
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy.get());
}
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy);
return defaultWebSecurityExpressionHandler;
}
}
package eu.dariah.de.dariahsp.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import eu.dariah.de.dariahsp.error.NotFoundException;
import eu.dariah.de.dariahsp.error.SAML2MetadataNotFoundException;
import eu.dariah.de.dariahsp.metadata.MetadataHelper;
@Controller
public class MetadataController {
@Autowired private MetadataHelper metadataHelper;
@GetMapping(value = {"/saml/metadata", "/saml/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();
} else {
metadata = metadataHelper.generateFromConfiguration();
}
if (metadata!=null) {
return metadata;
} else {
throw new SAML2MetadataNotFoundException();
}
}
}
......@@ -23,6 +23,8 @@ public class AuthInfoHandlerInterceptor extends HandlerInterceptorAdapter {
return;
}
AuthPojo auth = authInfoHelper.getAuth();
String sessionId = authInfoHelper.getOrCreateSessionId();
modelAndView.addObject("_sessionId", sessionId);
if (auth!=null && auth.isAuth()) {
modelAndView.addObject("_auth", auth);
setUserLocale(request, response, auth.getLanguage());
......
......@@ -4,6 +4,7 @@ import java.util.List;
import org.pac4j.core.context.JEEContext;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.core.util.Pac4jConstants;
import org.springframework.beans.factory.annotation.Autowired;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
import eu.dariah.de.dariahsp.model.web.AuthPojo;
......@@ -14,6 +15,10 @@ public class AuthInfoHelper {
@Autowired private JEEContext jeeContext;
@Autowired private ProfileManager<ExtendedUserProfile> profileManager;
@SuppressWarnings("unchecked")
public String getOrCreateSessionId() {
return jeeContext.getSessionStore().getOrCreateSessionId(jeeContext);
}
public AuthPojo getAuth() {
return this.getCurrentUserDetails();
......@@ -48,8 +53,12 @@ public class AuthInfoHelper {
pojo.setRoles(profile.getRoles());
pojo.setLevel(profile.getLevel());
pojo.setUserId(profile.getId());
pojo.setSessionId((jeeContext.getSessionStore()).getOrCreateSessionId(jeeContext));
pojo.setSessionId(this.getOrCreateSessionId());
}
return pojo;
}
public String getRequestedClientName() {
return jeeContext.getRequestParameter(Pac4jConstants.DEFAULT_CLIENT_NAME_PARAMETER).orElse(null);
}
}
# Config options of the dariahsp core library
# Commented properties reflect default values
auth:
#baseUrl: https://c105-229.cloud.gwdg.de/dme
salt: Qmwp4CO7LDkOUDouAcCcUqd9ZGNbRG5Jyr5lpntOuB9
rolehierarchy: ROLE_ADMINISTRATOR > ROLE_CONTRIBUTOR > ROLE_USER
roleDefinitions:
......@@ -44,7 +45,6 @@ auth:
sp:
#metadataResource: /data/_srv/dariahsp/sp_metadata.xml
maxAuthAge: -1
#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"
......
......@@ -2,11 +2,19 @@ plugins {
id 'war'
}
ext {
tilesVersion = "3.0.8"
jspApiVersion = "2.3.3"
}
dependencies {
implementation project(':dariahsp-core')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation "javax.servlet:jstl"
providedCompile "javax.servlet:javax.servlet-api"
providedCompile "javax.servlet.jsp:javax.servlet.jsp-api:$jspApiVersion"
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
......
......@@ -2,11 +2,12 @@ package eu.dariah.de.dariahsp.sample.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import eu.dariah.de.dariahsp.config.DefaultWebMvcConfigurer;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.config.web.AuthInfoConfigurer;
@Configuration
@Import({SecurityConfig.class, DefaultWebMvcConfigurer.class})
@Import({SecurityConfig.class, AuthInfoConfigurer.class})
public class SampleConfig {
......
......@@ -2,29 +2,32 @@ package eu.dariah.de.dariahsp.sample.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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 eu.dariah.de.dariahsp.config.CombinedSecurityConfigurationAdapter;
import eu.dariah.de.dariahsp.config.DefaultFiltersConfigurationAdapter;
import eu.dariah.de.dariahsp.config.web.SecurityConfigurerAdapter;
import eu.dariah.de.dariahsp.config.web.DefaultFiltersConfigurerAdapter;
@EnableWebSecurity
public class SampleWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Configuration
@Order(1)
public static class WebSecurityConfigAdapter extends CombinedSecurityConfigurationAdapter {
public static class WebSecurityConfigAdapter extends SecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/saml/**", "/form/**")
.antMatchers("/protected/**", "/blocked/**")
.and()
.authorizeRequests()
.antMatchers("/saml/admin.html").hasRole("CONTRIBUTOR")
.antMatchers("/saml/**").authenticated();
.expressionHandler(this.hierarchicalExpressionHandler())
.antMatchers("/protected/authenticated").authenticated()
.antMatchers("/protected/contributor").hasRole("CONTRIBUTOR")
.antMatchers("/protected/admin").hasRole("ADMINISTRATOR")
.antMatchers("/blocked/noaccess").denyAll();
super.configure(http);
}
......@@ -32,5 +35,5 @@ public class SampleWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Configuration
@Order(2)
public static class CallbackLoginLogoutConfigurationAdapter extends DefaultFiltersConfigurationAdapter {}
public static class CallbackLoginLogoutConfigurationAdapter extends DefaultFiltersConfigurerAdapter {}
}
package eu.dariah.de.dariahsp.controller;
package eu.dariah.de.dariahsp.sample.controller;
import java.util.Map;
......@@ -15,9 +15,12 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import eu.dariah.de.dariahsp.config.SecurityConfig;
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class ErrorController extends BasicErrorController {
@Autowired private SecurityConfig securityConfig;
@Autowired
public ErrorController(ErrorAttributes errorAttributes) {
......@@ -29,15 +32,9 @@ public class ErrorController extends BasicErrorController {
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> attr = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
final HttpStatus status = getStatus(request);
if (status == HttpStatus.UNAUTHORIZED) {
return new ModelAndView("error401");
} else if (status == HttpStatus.FORBIDDEN) {
return new ModelAndView("error403");
} else if (status == HttpStatus.NOT_FOUND) {