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

18: Adapt according to needs of first actual implementation (CR)

Task-Url: #18
parent e2f0564b
Pipeline #17981 passed with stage
in 1 minute and 45 seconds
package eu.dariah.de.dariahsp.config;
import org.pac4j.core.client.BaseClient;
import lombok.Data;
@Data
public class OrderedClient<T extends BaseClient<?>> implements Comparable<OrderedClient<?>> {
private final int order;
private final T client;
@Override
public int compareTo(OrderedClient<?> o) {
if (order<o.getOrder()) {
return -1;
} else if (order==o.getOrder()) {
return 0;
} else {
return 1;
}
}
}
\ No newline at end of file
......@@ -5,6 +5,7 @@ import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
......@@ -102,34 +103,34 @@ public class SecurityConfig {
}
@Bean
@SuppressWarnings("rawtypes")
public Config config(Optional<ProfileActionHandler> profileActionHandler) throws URISyntaxException {
List<Client> clients = new ArrayList<>();
SAML2Client samlClient = getSamlClient();
FormClient formClient = getFormClient();
List<OrderedClient<?>> clients = new ArrayList<>();
OrderedClient<SAML2Client> samlClient = getSamlClient();
OrderedClient<FormClient> formClient = getFormClient();
if (samlClient!=null) {
clients.add(samlClient);
}
if (formClient!=null) {
clients.add(formClient);
}
Collections.sort(clients);
Config.setProfileManagerFactory("customizableProfileManager", ctx -> new CustomizableProfileManager(ctx, profileActionHandler.orElse(null)));
Config c = new Config(new Clients(baseUrl().getAbsoluteUrl("/callback"), clients));
Config conf = new Config(
new Clients(baseUrl().getAbsoluteUrl("/callback"),
clients.stream().map(OrderedClient::getClient).collect(Collectors.toList())));
enabledClientNames = c.getClients().findAllClients().stream()
enabledClientNames = conf.getClients().findAllClients().stream()
.map(Client::getName)
.collect(Collectors.toList());
return c;
return conf;
}
private SAML2Client getSamlClient() throws URISyntaxException {
private OrderedClient<SAML2Client> getSamlClient() throws URISyntaxException {
if (!saml.isEnabled()) {
return null;
}
final SAML2Configuration cfg = new SAML2Configuration();
// Keystore
......@@ -175,17 +176,17 @@ public class SecurityConfig {
SAML2Client c = new SAML2Client(cfg);
c.setName(saml.getAuthorizerName());
c.setProfileCreator(saml2ProfileCreator());
c.setAuthenticator(new SAMLRequiredAttributeAuthenticator(cfg.getAttributeAsId(), cfg.getMappedAttributes(), saml.getSp()));
return c;
c.setAuthenticator(new SAMLRequiredAttributeAuthenticator(cfg.getAttributeAsId(), cfg.getMappedAttributes(), saml.getSp()));
return new OrderedClient<>(saml.getOrder(), c);
}
private FormClient getFormClient() throws URISyntaxException {
private OrderedClient<FormClient> getFormClient() throws URISyntaxException {
Optional<LocalUsernamePasswordAuthenticator> localUsernamePasswordAuthenticator = localUsernamePasswordAuthenticator();
if (localUsernamePasswordAuthenticator.isPresent()) {
FormClient c = new FormClient(baseUrl().getAbsoluteUrl("/login"), localUsernamePasswordAuthenticator.get());
c.setName(local.getAuthorizerName());
c.setProfileCreator(localProfileCreator());
return c;
return new OrderedClient<>(local.getOrder(), c);
}
return null;
}
......
......@@ -6,5 +6,6 @@ import lombok.Data;
public class LocalSecurity {
private boolean enabled;
private String authorizerName = "local";
private int order = 1;
private LocalUsers[] users;
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ import lombok.Setter;
public class SAMLSecurity {
private boolean enabled;
private String authorizerName = "saml";
private int order = 0;
private final KeystoreProperties keystore = new KeystoreProperties();
private final MetadataProperties metadata = new MetadataProperties();
private final ServiceProvider sp = new ServiceProvider();
......
package eu.dariah.de.dariahsp.web.controller;
import java.util.Map;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.JEEContext;
import org.pac4j.core.exception.http.HttpAction;
import org.pac4j.core.exception.http.RedirectionActionHelper;
import org.pac4j.core.http.adapter.JEEHttpActionAdapter;
import org.pac4j.http.client.indirect.FormClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.error.AuthenticatorNotAvailable;
import eu.dariah.de.dariahsp.web.AuthInfoHelper;
@Controller
public class CommonLoginController {
@Autowired protected AuthInfoHelper authInfoHelper;
@Autowired private SecurityConfig securityConfig;
@Autowired private Config config;
@Autowired private JEEContext jeeContext;
/**
* Present the local login form; override if the desired view is not 'login'
*
* @param map
* @param requestUrl
* @return login view
*/
@RequestMapping("/login")
public String loginForm(Map<String, Object> map, @RequestParam(value = "url", required = false) String requestUrl) {
final FormClient formClient = (FormClient) config.getClients().findClient(securityConfig.getLocal().getAuthorizerName()).orElseThrow();
map.put("callbackUrl", formClient.getCallbackUrl());
map.put("requestUrl", requestUrl);
return "login";
}
/**
* Mapping that requires authentication and thus redirects to configured client[0] if a user is not authenticated
* A provided callbackUrl is used for redirecting a user to a specified URL post-login.
*
* @param requestUrl
* @return redirection
*/
@PreAuthorize("isAuthenticated()")
@RequestMapping("/filteredLogin")
@ResponseBody
public String filteredLogin(@RequestParam(value = "url", required = false) String requestUrl) {
HttpAction action;
try {
action = RedirectionActionHelper.buildRedirectUrlAction(jeeContext, requestUrl!=null ? requestUrl : securityConfig.getDefaultLoginUrl());
} catch (final HttpAction e) {
action = e;
}
JEEHttpActionAdapter.INSTANCE.adapt(action, jeeContext);
return null;
}
/**
* Initiate a login based on a requested authentication client specified in terms of a client_name GET parameter
*
* @param client_name name of the authentication client
* @return redirection
*/
@RequestMapping("/loginClient")
@ResponseBody
public String forceLogin() {
final String clientName = authInfoHelper.getRequestedClientName();
final Client<?> client = config.getClients().findClient(clientName).orElse(null);
if (client==null) {
throw new AuthenticatorNotAvailable(clientName);
}
HttpAction action;
try {
action = client.getRedirectionAction(jeeContext).orElseThrow(() -> new AuthenticatorNotAvailable(clientName));
} catch (final HttpAction e) {
action = e;
}
JEEHttpActionAdapter.INSTANCE.adapt(action, jeeContext);
return null;
}
}
......@@ -33,6 +33,8 @@ auth:
local: ["application_user"]
saml: ["application_user"]
local:
# The client with the lowest number here is used when selecting a default authenticator
order: 0
# Enable local authentication
enabled: true
# Name of the method
......@@ -52,6 +54,7 @@ auth:
passhash: '$2y$10$nmTcpRxs.RFUstkJJms6U.AW61Jmr64s9VNQLuhpU8gYrgzCapwka'
roles: ["application_user"]
saml:
order: 1
# Enable SAML authentication
enabled: false
# Name of the method
......
......@@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
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.CommonLoginController;
import eu.dariah.de.dariahsp.web.controller.SAMLMetadataController;
@Configuration
......@@ -57,4 +58,14 @@ public class SampleConfig {
public SAMLMetadataController samlMetadataController() {
return new SAMLMetadataController();
}
/**
* Controller that binds to common login mappings
*
* @return LoginLogoutController bean
*/
@Bean
public CommonLoginController loginLogoutController() {
return new CommonLoginController();
}
}
......@@ -2,23 +2,13 @@ package eu.dariah.de.dariahsp.sample.controller;
import java.util.Map;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.JEEContext;
import org.pac4j.core.exception.http.HttpAction;
import org.pac4j.core.http.adapter.JEEHttpActionAdapter;
import org.pac4j.http.client.indirect.FormClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import eu.dariah.de.dariahsp.config.SecurityConfig;
import eu.dariah.de.dariahsp.error.AuthenticatorNotAvailable;
import eu.dariah.de.dariahsp.web.AuthInfoHelper;
/**
* Main controller of the sample application. All views are handled in the index JSP.
......@@ -32,11 +22,7 @@ public class SampleController {
private static String INDEX_PAGE = "index";
@Autowired private SecurityConfig securityConfig;
@Autowired private Config config;
@Autowired private JEEContext jeeContext;
@Autowired private AuthInfoHelper authInfoHelper;
@GetMapping("/")
public String home(Map<String, Object> map) {
this.assembleMap(map,
......@@ -94,33 +80,7 @@ public class SampleController {
"No access at all");
return INDEX_PAGE;
}
@RequestMapping("/login")
public String loginForm(Map<String, Object> map) {
final FormClient formClient = (FormClient) config.getClients().findClient(securityConfig.getLocal().getAuthorizerName()).get();
map.put("callbackUrl", formClient.getCallbackUrl());
return "login";
}
@RequestMapping("/forceLogin")
@ResponseBody
public String forceLogin() {
final String clientName = authInfoHelper.getRequestedClientName();
final Client<?> client = config.getClients().findClient(clientName).orElse(null);
if (client==null) {
throw new AuthenticatorNotAvailable(clientName);
}
HttpAction action;
try {
action = client.getRedirectionAction(jeeContext).orElseThrow(() -> new AuthenticatorNotAvailable(clientName));
} catch (final HttpAction e) {
action = e;
}
JEEHttpActionAdapter.INSTANCE.adapt(action, jeeContext);
return null;
}
private void assembleMap(Map<String, Object> map, String page, String restrictions) {
// _auth and _sessionId are inserted by the AuthInfoHandlerInterceptor
map.put("requestedPage", page);
......
#contextPath: /contextpath
#baseUrl: https://externally.visible.example.com${contextPath:/}
#contextPath: /dme
#baseUrl: https://c105-229.cloud.gwdg.de${contextPath:/}
baseUrl: http://localhost:8080
spring:
......@@ -35,6 +35,7 @@ auth:
local: ["application_user"]
saml: ["application_user"]
local:
order: 0
enabled: true
authorizerName: local
# Same password for each user: 1234
......@@ -49,7 +50,8 @@ auth:
passhash: '$2y$10$nmTcpRxs.RFUstkJJms6U.AW61Jmr64s9VNQLuhpU8gYrgzCapwka'
roles: ["application_user"]
saml:
enabled: false
order: 1
enabled: true
authorizerName: saml
keystore:
#path: /path/to/keystore.jks
......
......@@ -28,7 +28,7 @@
<b>SAML enabled</b>: ${samlEnabled}
<h2>Pages</h2>
<a href="<s:url value="/" />">Unprotected base url</a><br />
<a href="<s:url value="/protected/authenticated" />">Protected url: authentication required</a><br />
<a href="<s:url value="/protected/authenticated" var="protectedAuthenticated" />">Protected url: authentication required</a><br />
<a href="<s:url value="/method/authenticated" />">Protected url: authentication required (method annotation)</a><br />
<a href="<s:url value="/method/contributor" />">Protected url: CONTRIBUTOR role or higher required (method annotation)</a><br />
<a href="<s:url value="/protected/contributor" />">Protected url: CONTRIBUTOR role or higher required (security config)</a><br />
......@@ -44,8 +44,10 @@
<a href="<s:url value="/logout" />">local logout</a><br />
<a href="<s:url value="/centralLogout" />">central logout</a>
<br />
<a href="<s:url value="/forceLogin?client_name=saml" />">Force SAML login</a> (even if already authenticated)<br />
<a href="<s:url value="/forceLogin?client_name=local" />">Force local login</a> (even if already authenticated)<br />
<a href="<s:url value="/login?url=${protectedAuthenticated}" />">Enter local login form</a> (with callbackUrl)<br />
<a href="<s:url value="/filteredLogin?url=${protectedAuthenticated}" />">Enter filtered login</a> (with callbackUrl)<br />
<a href="<s:url value="/loginClient?client_name=saml" />">Force SAML login</a> (even if already authenticated)<br />
<a href="<s:url value="/loginClient?client_name=local" />">Force local login</a> (even if already authenticated)<br />
<br /><br />
</body>
</html>
\ No newline at end of file
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<!DOCTYPE HTML>
<html>
<head>
......@@ -7,7 +9,7 @@
<body>
<h1>Local login</h1>
<p>Default accounts <em>admin</em>, <em>contributor</em>, <em>user</em> (each with password <em>1234</em>)</p>
<form action="${callbackUrl}?client_name=local" method="POST">
<form action="${callbackUrl}?client_name=local&url=${requestUrl}" method="POST">
<input type="text" name="username" value="" />
<p />
<input type="password" name="password" value="" />
......
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