Commit 8cddb2b1 authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

Merge branch 'v2.0-dev' into 'v2.x-master'

v2.0-dev

See merge request !2
parents 8897f567 58fa7675
Pipeline #17902 passed with stage
in 1 minute and 49 seconds
......@@ -31,11 +31,11 @@ build:
- dariahsp-core/build/libs/*.jar
- dariahsp-sample-boot/build/libs/*.jar
only:
- v2.0
- v2.x-master
deploy:
stage: deploy
script:
- ./gradlew publish -x test $NEXUS_CREDENTIALS
only:
- v2.0
- v2.x-master
This diff is collapsed.
......@@ -5,7 +5,7 @@ plugins {
allprojects {
group = 'eu.dariah.de'
version = '2.0.0-SNAPSHOT'
version = '2.0.0-RELEASE'
repositories {
maven {
......
package eu.dariah.de.dariahsp.authentication;
import java.util.ArrayList;
import java.util.HashSet;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
......@@ -47,7 +48,12 @@ public class LocalUsernamePasswordAuthenticator implements Authenticator<Usernam
final CommonProfile profile = new CommonProfile();
profile.setId(username);
profile.addAttribute(Pac4jConstants.USERNAME, username);
profile.setRoles(cnf.getRoles());
if (cnf.getRoles()==null) {
profile.setRoles(new HashSet<>(0));
} else {
profile.setRoles(cnf.getRoles());
}
credentials.setUserProfile(profile);
log.info("Local authentication succeeded [{}]", profile.toString());
......
......@@ -6,8 +6,8 @@ import java.util.Set;
import lombok.Data;
@Data
public class RoleDefinition {
private String role;
public class PermissionDefinition {
private String permissionSet;
private int level;
private Map<String,Set<String>> mappings;
private Map<String,Set<String>> roleMappings;
}
......@@ -48,8 +48,8 @@ public class SecurityConfig {
private final SAMLSecurity saml = new SAMLSecurity();
private String salt;
private String roleHierarchy;
private List<RoleDefinition> roleDefinitions;
private String permissionHierarchy = "";
private List<PermissionDefinition> permissionDefinitions;
private String baseUrl = "http://localhost:8080";
private String defaultLoginUrl = null;
private String defaultLogoutUrl = null;
......@@ -87,8 +87,8 @@ public class SecurityConfig {
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy(roleHierarchy);
log.info("RoleHierarchy configured: {}", roleHierarchy);
r.setHierarchy(permissionHierarchy);
log.info("PermissionHierarchy configured: {}", permissionHierarchy);
return r;
}
......@@ -146,7 +146,13 @@ public class SecurityConfig {
cfg.setMaximumAuthenticationLifetime(saml.getSp().getMaxAuthAge());
cfg.setSignatureAlgorithms(saml.getSp().getSigningMethods());
cfg.setSignatureReferenceDigestMethods(saml.getSp().getDigestMethods());
cfg.setServiceProviderEntityId(saml.getSp().getEntityId());
if (saml.getSp().getEntityId()!=null) {
cfg.setServiceProviderEntityId(saml.getSp().getEntityId());
} else {
cfg.setServiceProviderEntityId(baseUrl);
}
cfg.setSpLogoutRequestSigned(saml.getSp().isLogoutRequestSigned());
cfg.setWantsAssertionsSigned(saml.getSp().isWantsAssertionsSigned());
cfg.setWantsResponsesSigned(saml.getSp().isWantsResponsesSigned());
......@@ -176,13 +182,13 @@ public class SecurityConfig {
private SamlProfileCreator saml2ProfileCreator() {
SamlProfileCreator saml2ProfileCreator = new SamlProfileCreator(this, saml.getAuthorizerName());
saml2ProfileCreator.setRoleDefinitions(roleDefinitions);
saml2ProfileCreator.setPermissionDefinitions(permissionDefinitions);
return saml2ProfileCreator;
}
private LocalProfileCreator localProfileCreator() {
LocalProfileCreator localProfileCreator = new LocalProfileCreator(local.getAuthorizerName());
localProfileCreator.setRoleDefinitions(roleDefinitions);
localProfileCreator.setPermissionDefinitions(permissionDefinitions);
return localProfileCreator;
}
}
......@@ -5,7 +5,7 @@ import lombok.Setter;
@Getter @Setter
public class SAMLSecurity {
private boolean enabled = true;
private boolean enabled;
private String authorizerName = "saml";
private final KeystoreProperties keystore = new KeystoreProperties();
private final MetadataProperties metadata = new MetadataProperties();
......
......@@ -23,7 +23,7 @@ public class ServiceProvider {
private int maxAuthAge = 3600;
private String entityId;
private int httpClientTimoutMs = 2000;
private boolean signMetadata;
private boolean signMetadata = true;
private List<String> signingMethods;
private List<String> digestMethods;
private List<String> supportedProtocols;
......
......@@ -8,10 +8,20 @@ import eu.dariah.de.dariahsp.web.AuthInfoHandlerInterceptor;
import eu.dariah.de.dariahsp.web.AuthInfoHelper;
import lombok.extern.slf4j.Slf4j;
/**
* WebMvcConfigurer responsible for injecting the {@link AuthInfoHandlerInterceptor} into the filter chain, an interceptor
* that is responsible for providing basic authentication and authorization information into the model.
*
* @author Tobias Gradl
*
*/
@Slf4j
public class AuthInfoConfigurer implements WebMvcConfigurer {
@Autowired private AuthInfoHelper authInfoHelper;
/**
* Adds an {@link AuthInfoHandlerInterceptor} to the {@link InterceptorRegistry}
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInfoHandlerInterceptor());
......
......@@ -5,7 +5,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import eu.dariah.de.dariahsp.config.RoleDefinition;
import eu.dariah.de.dariahsp.config.PermissionDefinition;
import eu.dariah.de.dariahsp.model.ExtendedUserProfile;
import lombok.Data;
......@@ -13,18 +13,18 @@ import lombok.Data;
public abstract class BaseProfileCreator {
public static final String ROLE_PREFIX = "ROLE_";
protected final String clientName;
protected List<RoleDefinition> roleDefinitions;
protected List<PermissionDefinition> permissionDefinitions;
protected Set<RoleDefinition> getMappedRoles(ExtendedUserProfile profile) {
Set<RoleDefinition> roles = new LinkedHashSet<>();
for (RoleDefinition rm : roleDefinitions) {
for (String client : rm.getMappings().keySet()) {
protected Set<PermissionDefinition> getMappedRoles(ExtendedUserProfile profile) {
Set<PermissionDefinition> roles = new LinkedHashSet<>();
for (PermissionDefinition rm : permissionDefinitions) {
for (String client : rm.getRoleMappings().keySet()) {
if (!client.equals(clientName)) {
continue;
}
for (String extRole : profile.getExternalRoles()) {
if (!extRole.trim().isEmpty() &&
rm.getMappings().get(client).contains(extRole.trim()) &&
rm.getRoleMappings().get(client).contains(extRole.trim()) &&
!roles.contains(rm)) {
roles.add(rm);
}
......@@ -37,14 +37,14 @@ public abstract class BaseProfileCreator {
protected void mapAndAssignRoles(ExtendedUserProfile profile) {
if (this.canMapRoles(profile)) {
// Determine applicable role definitions
Set<RoleDefinition> mappedDefinitions = this.getMappedRoles(profile);
Set<PermissionDefinition> mappedDefinitions = this.getMappedRoles(profile);
// Set applicable roles as Strings
profile.setRoles(mappedDefinitions.stream()
.map(mr -> ROLE_PREFIX + mr.getRole().toUpperCase())
.map(pd -> pd.getPermissionSet().toUpperCase())
.collect(Collectors.toSet()));
// Set maximum applicable level
profile.setLevel(mappedDefinitions.stream()
.mapToInt(RoleDefinition::getLevel)
.mapToInt(PermissionDefinition::getLevel)
.max().orElse(0));
}
}
......@@ -52,6 +52,6 @@ public abstract class BaseProfileCreator {
private boolean canMapRoles(ExtendedUserProfile profile) {
return clientName!=null &&
profile.getExternalRoles()!=null && !profile.getExternalRoles().isEmpty() &&
roleDefinitions!=null && !roleDefinitions.isEmpty();
permissionDefinitions!=null && !permissionDefinitions.isEmpty();
}
}
}
\ No newline at end of file
......@@ -10,6 +10,11 @@ import org.springframework.web.servlet.support.RequestContextUtils;
import eu.dariah.de.dariahsp.web.model.AuthPojo;
/**
* Interceptor that adds basic authentication and authorization information as {@link AuthPojo} to each request model
*
* @author Tobias Gradl
*/
public class AuthInfoHandlerInterceptor extends HandlerInterceptorAdapter {
private AuthInfoHelper authInfoHelper;
......@@ -17,6 +22,9 @@ public class AuthInfoHandlerInterceptor extends HandlerInterceptorAdapter {
public void setAuthInfoHelper(AuthInfoHelper authInfoHelper) { this.authInfoHelper = authInfoHelper; }
/*
* Adds the _sessionId and _auth request model attributes
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView==null) {
......
# Config options of the dariahsp core library
# Commented properties reflect default values
auth:
# Base externally visible URL
#baseUrl: http://localhost:8080
# Default redirected URL post login
#defaultLoginUrl: ${auth.baseUrl}
# Default redirected URL post logout
#defaultLogoutUrl: ${auth.baseUrl}
# Salt for signing and encryption purposes
salt: Qmwp4CO7LDkOUDouAcCcUqd9ZGNbRG5Jyr5lpntOuB9
rolehierarchy: ROLE_ADMINISTRATOR > ROLE_CONTRIBUTOR > ROLE_USER
roleDefinitions:
- role: ADMINISTRATOR
# Hierarchy used in role-based authorization voting
permissionHierarchy: ROLE_ADMINISTRATOR > ROLE_CONTRIBUTOR > ROLE_USER
# Permission sets to code against and mapping to 'external' roles
permissionDefinitions:
# Name of the permission set (internal role)
- permissionSet: ROLE_ADMINISTRATOR
# Numerical authorization level allowing security expressions as level gte 50
level: 100
mappings:
roleMappings:
# Role mapping to locally configured roles
local: ["application_admin"]
# Role mapping to SAML (typically memberOf) roles
saml: ["application_admin"]
- role: CONTRIBUTOR
- permissionSet: ROLE_CONTRIBUTOR
level: 50
mappings:
roleMappings:
local: ["application_contributor"]
saml: ["application_contributor"]
- role: USER
- permissionSet: ROLE_USER
level: 10
mappings:
roleMappings:
local: ["application_user"]
saml: ["application_user"]
local:
# Enable local authentication
enabled: true
authorizerName: local
# Name of the method
#authorizerName: local
# Same password for each user: 1234
users:
# Username
- username: 'admin'
# BCrypt hashed password
passhash: '$2y$10$nmTcpRxs.RFUstkJJms6U.AW61Jmr64s9VNQLuhpU8gYrgzCapwka'
# Pseudo-external role
roles: ["application_admin"]
- username: 'contributor'
passhash: '$2y$10$nmTcpRxs.RFUstkJJms6U.AW61Jmr64s9VNQLuhpU8gYrgzCapwka'
......@@ -37,43 +52,57 @@ auth:
passhash: '$2y$10$nmTcpRxs.RFUstkJJms6U.AW61Jmr64s9VNQLuhpU8gYrgzCapwka'
roles: ["application_user"]
saml:
# Enable SAML authentication
enabled: false
authorizerName: saml
# Name of the method
#authorizerName: saml
# Java KeyStore configuration
keystore:
path: /data/_srv/dariahsp/c105-229.cloud.gwdg.de.jks
pass: clariah
alias: c105-229.cloud.gwdg.de
aliaspass: clariah6
path: /path/to/keystore.jks
pass: keystore_password
alias: keypair_alias
aliaspass: private_key_password
# IdP configuration
metadata:
# URL of IdP metadata
url: https://aaiproxy.de.dariah.eu/idp/
# Hosted SP configuration
sp:
# Metadata in filesystem (if available, otherwise generated)
#metadataResource: /data/_srv/dariahsp/sp_metadata.xml
maxAuthAge: -1
# Maximum authentication lifetime in seconds
# maxAuthAge: 3600
#entityId: ${baseUrl}
signMetadata: true
# Signature configuration
#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
#authnRequestSigned: true
#logoutRequestSigned: true
#wantsAssertionsSigned: true
#wantsResponsesSigned: false
# SAML SP protocol configuration
#supportedProtocols: urn:oasis:names:tc:SAML:2.0:protocol
authnRequestSigned: truevv
logoutRequestSigned: true
wantsAssertionsSigned: true
wantsResponsesSigned: false
httpClientTimoutMs: 2000
# Timeout for interaction with configured IdP
#httpClientTimoutMs: 2000
# URL for redirection after RequiredAttributesException is raised
attributesIncompleteRedirectUrl: https://auth.de.dariah.eu/cgi-bin/selfservice/ldapportal.pl
# Attribute groups for attribute mapping and required attribute definition
attributeGroups:
# All attributes are required
- check: AND
attributes:
- friendlyName: mail
name: urn:oid:0.9.2342.19200300.100.1.3
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
- friendlyName: dariahTermsOfUse
name: urn:oid:1.3.6.1.4.1.10126.1.52.4.15
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
# A required value of the attribute can be defined
#value: Terms_of_Use_germ_engl_v6.pdf
- friendlyName: eduPersonPrincipalName
mappedAttribute: id
name: urn:oid:1.3.6.1.4.1.5923.1.1.1.6
nameFormat: urn:oasis:names:tc:SAML:2.0:attrname-format:uri
# Optional attributes are typically used for mapping SAML attributes to ExtendedUserProfile propeties
# like username, externalRoles below
- check: OPTIONAL
attributes:
- friendlyName: mail
......
## DARIAHSP - Sample boot app
\ No newline at end of file
## DARIAHSP - Sample boot app
This Spring Boot application serves as simple reference implementation of the [dariahsp-core](../dariashp-core) library. The sample is based on Java ServerPages (JSP) for view rendering and presents itself with login, logout and protected areas.
> See the JavaDoc for further explanation on the components of the sample application
### Initialization
1. [`SampleApplication`](/src/main/java/eu/dariah/de/dariahsp/sample/SampleApplication.java) is the Spring Boot application class and handles initialization
### Configuration
2. [`SampleConfig`](/src/main/java/eu/dariah/de/dariahsp/sample/config/SampleApplication.java) serves as primary application configuration class; it defines the beans of
* `profileActionPostprocessor` (optional) for processing of login and logout activity,
* `samlMetadataController`, a controller bean that facilitates access to metadata of the SP and
* `webServerFactoryCustomizer` for changing the context path of the application
> With a optional custom implementation of the `profileActionPostprocessor`, the `SampleConfig` class can be used as a starting point for configuring futher aspects of the implementing application
3. [`SampleSecurityConfig`](/src/main/java/eu/dariah/de/dariahsp/sample/config/SampleSecurityConfig.java) - by extending the basic `SecurityConfig` class - imports the beans and configuration of the core library; it further imports configuration of the `AuthInfoConfigurer` class; The `@ConfigurationProperties(prefix = "auth")` annotation provides all configuration properties to the implemented `dariahsp-core` configuration
> The `SampleSecurityConfig` can be used 'as is' in other implementations as all security-related beans are soundly configured
4. [`SampleWebSecurityConfig`](/src/main/java/eu/dariah/de/dariahsp/sample/config/SampleWebSecurityConfig.java) is the basic `WebSecurityConfigurerAdapter` of the sample application and specifies URL and authorization patterns that are specific to the sample application
> The `SampleWebSecurityConfig` can be used as a starting point for configuring protected areas of the implementing application, but will probably require adaption to existing requirements
### Controllers
5. The [`SampleController`](/src/main/java/eu/dariah/de/dariahsp/sample/controller/SampleController.java) configures the request mappings and views of the sample application
6. [`ErrorController`](/src/main/java/eu/dariah/de/dariahsp/sample/controller/ErrorController.java) as implementation of Spring's `BasicErrorController` configures handling of exceptions that could occur in the application. For the sample application, all errors (e.g. 403 errors attempting to access protected areas) are dispatched to the `index` view, providing error messages
### Profile actions
7. [`SampleProfileActionHandler`](/src/main/java/eu/dariah/de/dariahsp/sample/profiles/SampleProfileActionHandler.java) is an empty implementation of the `ProfileActionHandler` interface - a bean provided that is setup in [`SampleConfig`](/src/main/java/eu/dariah/de/dariahsp/sample/config/SampleApplication.java). Customize behavior upon login and logout e.g. to persist user information in a database or to load save custom attributes of users and adding them to the attribute set of the handled `ExtendedUserProfile`
### Resources
8. [`application.yml`](/src/main/resources/application.yml) is a sample application configuration provided with this application
9. [`logback.xml`](/src/main/resources/application.yml) customizes logging through [logback](http://logback.qos.ch/)
10. [`sample_keystore.jks`](/src/main/resources/application.yml) is an example Java KeyStore that can be used for initial testing
### JSP views
11. [`index.jsp`](/src/main/webapp/WEB-INF/views/index.jsp) is the main view of the sample application that serves all succeeding and error requests.
12. [`login.jsp`](/src/main/webapp/WEB-INF/views/login.jsp) is the form for querying username and passwords of `local` logins.
\ No newline at end of file
......@@ -6,19 +6,7 @@ dependencies {
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'
//providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation librarySets.commonTest
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
bootJar {
......
......@@ -3,22 +3,15 @@ package eu.dariah.de.dariahsp.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import eu.dariah.de.dariahsp.ProfileActionHandler;
import eu.dariah.de.dariahsp.sample.profiles.SampleProfileActionHandler;
/**
* Spring Boot application class
*
* @author Tobias Gradl
*/
@SpringBootApplication
@ConfigurationPropertiesScan
@ComponentScan({"eu.dariah.de.dariahsp.sample", "eu.dariah.de.dariahsp.web.controller"})
public class SampleApplication {
@Bean
public ProfileActionHandler profileActionPostprocessor() {
return new SampleProfileActionHandler();
}
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
......
package eu.dariah.de.dariahsp.sample.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import eu.dariah.de.dariahsp.CustomizableProfileManager;
import eu.dariah.de.dariahsp.ProfileActionHandler;
import eu.dariah.de.dariahsp.sample.profiles.SampleProfileActionHandler;
import eu.dariah.de.dariahsp.web.controller.SAMLMetadataController;
@Data
@Slf4j
@Configuration
@ConfigurationProperties
public class SampleConfig {
private static final Logger log = LoggerFactory.getLogger(SampleConfig.class);
private String contextPath = "";
public String getContextPath() { return contextPath; }
public void setContextPath(String contextPath) { this.contextPath = contextPath; }
/**
* WebServerFactoryCustomizer bean that adapts to a configured context path for the application. This adaption is not
* necessary for implementation of the dariahsp-core library, but helps with setting up the application as it might
* be available or proxied at their deployments
*
* @return WebServerFactoryCustomizer
*/
@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer() {
log.info("Web server context path set to {}", contextPath.isEmpty() ? "/" : contextPath);
return factory -> factory.setContextPath(contextPath);
}
/**
* Bean that is injected into {@link CustomizableProfileManager} to facilitate observation of login and logout actions
* Implementations can provide custom implementations of the {@link ProfileActionHandler} interface e.g. to log such
* actions into a database
*
* @return SampleProfileActionHandler bean
*/
@Bean
public ProfileActionHandler profileActionPostprocessor() {
return new SampleProfileActionHandler();
}
/**
* Controller bean that facilitates access to generated or stored SAML SP metadata
*
* @return SAMLMetadataController bean
*/
@Bean
public SAMLMetadataController samlMetadataController() {
return new SAMLMetadataController();
}
}
......@@ -6,11 +6,16 @@ import org.springframework.context.annotation.Import;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.config.web.AuthInfoConfigurer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import eu.dariah.de.dariahsp.web.AuthInfoHandlerInterceptor;
@Data
@EqualsAndHashCode(callSuper=false)
/**
* Main security configuration extends {@link SecurityConfig}. The auth namespace of the configuration properties
* is utilized for the configuration of the extended {@link SecurityConfig}.
*
* Import of the {@link AuthInfoConfigurer} class ultimately ensures configuration of the {@link AuthInfoHandlerInterceptor}
*
* @author Tobias Gradl
*/
@Configuration
@ConfigurationProperties(prefix = "auth")
@Import({AuthInfoConfigurer.class})
......
......@@ -9,9 +9,19 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import eu.dariah.de.dariahsp.config.web.SecurityConfigurerAdapter;
import eu.dariah.de.dariahsp.config.web.DefaultFiltersConfigurerAdapter;
/**
* Web security configuration addressing protected areas and authorization patterns for this sample application
*
* @author Tobias Gradl
*/
@EnableWebSecurity
public class SampleWebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Adapt this as required in a target application
*
* @author Tobias Gradl
*/
@Configuration
@Order(1)
public static class WebSecurityConfigAdapter extends SecurityConfigurerAdapter {
......@@ -32,6 +42,11 @@ public class SampleWebSecurityConfig extends WebSecurityConfigurerAdapter {
}
}
/**
* Make sure to include this for logout, login and callback filters
*