Skip to content
Snippets Groups Projects
Commit faa79935 authored by Jérôme LELEU's avatar Jérôme LELEU Committed by Makhtar DIAGNE
Browse files

handle provided username

parent 55ac2b80
No related branches found
No related tags found
1 merge request!1Feature/design/1
......@@ -45,7 +45,6 @@ import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
import fr.gouv.vitamui.iam.external.client.CasExternalRestClient;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.audit.AuditableExecution;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
......@@ -54,7 +53,6 @@ import org.apereo.cas.pm.PasswordManagementService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.TicketFactory;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.io.CommunicationsManager;
import org.apereo.cas.web.DelegatedClientWebflowManager;
......@@ -178,18 +176,10 @@ public class WebflowConfig {
@Qualifier("centralAuthenticationService")
private ObjectProvider<CentralAuthenticationService> centralAuthenticationService;
@Autowired
@Qualifier("authenticationEventExecutionPlan")
private ObjectProvider<AuthenticationEventExecutionPlan> authenticationEventExecutionPlan;
@Autowired
@Qualifier("singleSignOnParticipationStrategy")
private ObjectProvider<SingleSignOnParticipationStrategy> webflowSingleSignOnParticipationStrategy;
@Autowired
@Qualifier("defaultTicketRegistrySupport")
private ObjectProvider<TicketRegistrySupport> ticketRegistrySupport;
@Autowired
@Qualifier("delegatedClientDistributedSessionStore")
private ObjectProvider<SessionStore> delegatedClientDistributedSessionStore;
......@@ -198,14 +188,14 @@ public class WebflowConfig {
private String vitamuiPortalUrl;
@Value("${cas.authn.surrogate.separator}")
private String surrogationSeperator;
private String surrogationSeparator;
@Autowired
private Utils utils;
@Bean
public DispatcherAction dispatcherAction() {
return new DispatcherAction(providersService, identityProviderHelper, casRestClient, surrogationSeperator, utils);
return new DispatcherAction(providersService, identityProviderHelper, casRestClient, surrogationSeparator, utils);
}
@Bean
......@@ -215,22 +205,6 @@ public class WebflowConfig {
ticketRegistry, ticketFactory);
}
@RefreshScope
@Bean
public Action initialFlowSetupAction() {
return new CustomInitialFlowSetupAction(CollectionUtils.wrap(argumentExtractor.getObject()),
servicesManager.getObject(),
authenticationRequestServiceSelectionStrategies.getObject(),
ticketGrantingTicketCookieGenerator.getObject(),
warnCookieGenerator.getObject(),
casProperties,
authenticationEventExecutionPlan.getObject(),
webflowSingleSignOnParticipationStrategy.getObject(),
ticketRegistrySupport.getObject(),
vitamuiPortalUrl,
surrogationSeperator);
}
@Bean
public SelectRedirectAction selectRedirectAction() {
return new SelectRedirectAction();
......@@ -255,7 +229,7 @@ public class WebflowConfig {
@Bean
@Lazy
public Action delegatedAuthenticationAction() {
return new AutomaticDelegatedClientAuthenticationAction(
return new CustomDelegatedClientAuthenticationAction(
initialAuthenticationAttemptWebflowEventResolver.getObject(),
serviceTicketRequestWebflowEventResolver.getObject(),
adaptiveAuthenticationPolicy.getObject(),
......@@ -269,7 +243,13 @@ public class WebflowConfig {
centralAuthenticationService.getObject(),
webflowSingleSignOnParticipationStrategy.getObject(),
delegatedClientDistributedSessionStore.getObject(),
CollectionUtils.wrap(argumentExtractor.getObject()));
CollectionUtils.wrap(argumentExtractor.getObject()),
identityProviderHelper,
providersService,
utils,
ticketRegistry,
vitamuiPortalUrl,
surrogationSeparator);
}
@RefreshScope
......
......@@ -38,6 +38,7 @@ package fr.gouv.vitamui.cas.webflow.actions;
import fr.gouv.vitamui.cas.provider.SamlIdentityProviderDto;
import fr.gouv.vitamui.cas.provider.ProvidersService;
import fr.gouv.vitamui.cas.util.Constants;
import fr.gouv.vitamui.cas.util.Utils;
import fr.gouv.vitamui.commons.api.CommonConstants;
import fr.gouv.vitamui.commons.api.logger.VitamUILogger;
......@@ -50,6 +51,7 @@ import org.apereo.cas.audit.AuditableExecution;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.TicketGrantingTicket;
......@@ -65,7 +67,7 @@ import org.pac4j.core.client.Clients;
import org.pac4j.core.context.JEEContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.saml.client.SAML2Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
......@@ -77,46 +79,61 @@ import java.util.List;
import java.util.Optional;
/**
* Regular authentication delegation + automatic delegation given the provided IdP.
* Custom authentication delegation:
* - automatic delegation given the provided IdP
* - extraction of the username/surrogate passed as a request parameter.
*
*
*/
public class AutomaticDelegatedClientAuthenticationAction extends DelegatedClientAuthenticationAction {
private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(AutomaticDelegatedClientAuthenticationAction.class);
@Autowired
private IdentityProviderHelper identityProviderHelper;
@Autowired
private ProvidersService providersService;
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
private Utils utils;
@Autowired
private TicketRegistry ticketRegistry;
public AutomaticDelegatedClientAuthenticationAction(final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
final Clients clients,
final ServicesManager servicesManager,
final AuditableExecution delegatedAuthenticationPolicyEnforcer,
final DelegatedClientWebflowManager delegatedClientWebflowManager,
final AuthenticationSystemSupport authenticationSystemSupport,
final CasConfigurationProperties casProperties,
final AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies,
final CentralAuthenticationService centralAuthenticationService,
final SingleSignOnParticipationStrategy singleSignOnParticipationStrategy,
final SessionStore<JEEContext> sessionStore, final List<ArgumentExtractor> argumentExtractors) {
public class CustomDelegatedClientAuthenticationAction extends DelegatedClientAuthenticationAction {
private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(CustomDelegatedClientAuthenticationAction.class);
private final IdentityProviderHelper identityProviderHelper;
private final ProvidersService providersService;
private final CasConfigurationProperties casProperties;
private final Utils utils;
private final TicketRegistry ticketRegistry;
private final String vitamuiPortalUrl;
private final String surrogationSeparator;
public CustomDelegatedClientAuthenticationAction(final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
final Clients clients,
final ServicesManager servicesManager,
final AuditableExecution delegatedAuthenticationPolicyEnforcer,
final DelegatedClientWebflowManager delegatedClientWebflowManager,
final AuthenticationSystemSupport authenticationSystemSupport,
final CasConfigurationProperties casProperties,
final AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies,
final CentralAuthenticationService centralAuthenticationService,
final SingleSignOnParticipationStrategy singleSignOnParticipationStrategy,
final SessionStore<JEEContext> sessionStore,
final List<ArgumentExtractor> argumentExtractors,
final IdentityProviderHelper identityProviderHelper,
final ProvidersService providersService,
final Utils utils,
final TicketRegistry ticketRegistry,
final String vitamuiPortalUrl,
final String surrogationSeparator) {
super(initialAuthenticationAttemptWebflowEventResolver, serviceTicketRequestWebflowEventResolver, adaptiveAuthenticationPolicy,
clients, servicesManager, delegatedAuthenticationPolicyEnforcer, delegatedClientWebflowManager, authenticationSystemSupport,
casProperties, authenticationRequestServiceSelectionStrategies, centralAuthenticationService, singleSignOnParticipationStrategy,
sessionStore, argumentExtractors);
this.identityProviderHelper = identityProviderHelper;
this.casProperties = casProperties;
this.providersService = providersService;
this.utils = utils;
this.ticketRegistry = ticketRegistry;
this.vitamuiPortalUrl = vitamuiPortalUrl;
this.surrogationSeparator = surrogationSeparator;
}
@Override
......@@ -125,6 +142,28 @@ public class AutomaticDelegatedClientAuthenticationAction extends DelegatedClien
final Event event = super.doExecute(context);
if ("error".equals(event.getId())) {
final MutableAttributeMap<Object> flowScope = context.getFlowScope();
flowScope.put(Constants.PORTAL_URL, vitamuiPortalUrl);
String username = context.getRequestParameters().get(Constants.USERNAME);
if (username != null) {
username = username.toLowerCase();
LOGGER.debug("Provided username: {}", username);
if (username.startsWith(surrogationSeparator)) {
username = org.apache.commons.lang.StringUtils.substringAfter(username, surrogationSeparator);
}
WebUtils.putCredential(context, new UsernamePasswordCredential(username, null));
if (username.contains(surrogationSeparator)) {
final String[] parts = username.split("\\" + surrogationSeparator);
flowScope.put(Constants.SURROGATE, parts[0]);
flowScope.put(Constants.SUPER_USER, parts[1]);
}
}
TicketGrantingTicket tgt = null;
final String tgtId = WebUtils.getTicketGrantingTicketId(context);
if (tgtId != null) {
......
/**
* Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020)
* and the signatories of the "VITAM - Accord du Contributeur" agreement.
*
* contact@programmevitam.fr
*
* This software is a computer program whose purpose is to implement
* implement a digital archiving front-office system for the secure and
* efficient high volumetry VITAM solution.
*
* This software is governed by the CeCILL-C license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL-C
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package fr.gouv.vitamui.cas.webflow.actions;
import fr.gouv.vitamui.cas.util.Constants;
import fr.gouv.vitamui.commons.api.logger.VitamUILogger;
import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory;
import org.apache.commons.lang.StringUtils;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.web.cookie.CasCookieBuilder;
import org.apereo.cas.web.flow.SingleSignOnParticipationStrategy;
import org.apereo.cas.web.flow.login.InitialFlowSetupAction;
import org.apereo.cas.web.support.ArgumentExtractor;
import org.apereo.cas.web.support.WebUtils;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* Custom initial flow action to retrieve pre-filled inputs.
*
*
*/
public class CustomInitialFlowSetupAction extends InitialFlowSetupAction {
private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(CustomInitialFlowSetupAction.class);
private final String vitamuiPortalUrl;
private final String surrogationSeperator;
@Value("${theme.vitam-logo:#{null}}")
private String vitamLogoPath;
@Value("${theme.vitamui-logo-large:#{null}}")
private String vitamuiLargeLogoPath;
public CustomInitialFlowSetupAction(final List<ArgumentExtractor> argumentExtractors,
final ServicesManager servicesManager,
final AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies,
final CasCookieBuilder ticketGrantingTicketCookieGenerator,
final CasCookieBuilder warnCookieGenerator,
final CasConfigurationProperties casProperties,
final AuthenticationEventExecutionPlan authenticationEventExecutionPlan,
final SingleSignOnParticipationStrategy renewalStrategy,
final TicketRegistrySupport ticketRegistrySupport,
final String vitamuiPortalUrl,
final String surrogationSeperator) {
super(argumentExtractors, servicesManager, authenticationRequestServiceSelectionStrategies, ticketGrantingTicketCookieGenerator,
warnCookieGenerator, casProperties, authenticationEventExecutionPlan, renewalStrategy, ticketRegistrySupport);
this.vitamuiPortalUrl = vitamuiPortalUrl;
this.surrogationSeperator = surrogationSeperator;
}
@Override
public Event doExecute(final RequestContext context) {
final MutableAttributeMap<Object> flowScope = context.getFlowScope();
flowScope.put(Constants.PORTAL_URL, vitamuiPortalUrl);
String username = context.getRequestParameters().get(Constants.USERNAME);
if (username != null) {
username = username.toLowerCase();
LOGGER.debug("Provided username: {}", username);
if (username.startsWith(surrogationSeperator)) {
username = StringUtils.substringAfter(username, surrogationSeperator);
}
WebUtils.putCredential(context, new UsernamePasswordCredential(username, null));
if (username.contains(surrogationSeperator)) {
final String[] parts = username.split("\\" + surrogationSeperator);
flowScope.put(Constants.SURROGATE, parts[0]);
flowScope.put(Constants.SUPER_USER, parts[1]);
}
}
if(vitamLogoPath != null) {
try {
Path logoFile = Paths.get(vitamLogoPath);
String logo = DatatypeConverter.printBase64Binary(Files.readAllBytes(logoFile));
flowScope.put(Constants.VITAM_LOGO, logo);
} catch (IOException e) {
LOGGER.warn("Can't find vitam logo");
e.printStackTrace();
}
}
if(vitamuiLargeLogoPath != null) {
try {
Path logoFile = Paths.get(vitamuiLargeLogoPath);
String logo = DatatypeConverter.printBase64Binary(Files.readAllBytes(logoFile));
flowScope.put(Constants.VITAM_UI_LARGE_LOGO, logo);
} catch (IOException e) {
LOGGER.warn("Can't find vitam ui large logo");
e.printStackTrace();
}
}
return super.doExecute(context);
}
}
......@@ -82,19 +82,19 @@ public class DispatcherAction extends AbstractAction {
private final CasExternalRestClient casExternalRestClient;
private final String surrogationSeperator;
private final String surrogationSeparator;
private final Utils utils;
public DispatcherAction(final ProvidersService providersService,
final IdentityProviderHelper identityProviderHelper,
final CasExternalRestClient casExternalRestClient,
final String surrogationSeperator,
final String surrogationSeparator,
final Utils utils) {
this.providersService = providersService;
this.identityProviderHelper = identityProviderHelper;
this.casExternalRestClient = casExternalRestClient;
this.surrogationSeperator = surrogationSeperator;
this.surrogationSeparator = surrogationSeparator;
this.utils = utils;
}
......@@ -105,12 +105,12 @@ public class DispatcherAction extends AbstractAction {
final String username = credential.getUsername().toLowerCase();
String dispatchedUser = username;
String surrogate = null;
if (username.contains(surrogationSeperator)) {
dispatchedUser = StringUtils.substringAfter(username, surrogationSeperator);
if (username.startsWith(surrogationSeperator)) {
if (username.contains(surrogationSeparator)) {
dispatchedUser = StringUtils.substringAfter(username, surrogationSeparator);
if (username.startsWith(surrogationSeparator)) {
WebUtils.putCredential(requestContext, new UsernamePasswordCredential(dispatchedUser, null));
} else {
surrogate = StringUtils.substringBefore(username, surrogationSeperator);
surrogate = StringUtils.substringBefore(username, surrogationSeparator);
}
}
LOGGER.debug("Dispatching user: {} / surrogate: {}", dispatchedUser, surrogate);
......
......@@ -9,6 +9,8 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.webflow.context.servlet.ServletExternalContext;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.FlowVariable;
import org.springframework.webflow.execution.FlowExecutionContext;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.RequestContext;
......@@ -21,6 +23,8 @@ import javax.servlet.http.HttpSession;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import lombok.val;
/**
* A base webflow action test.
*
......@@ -52,6 +56,11 @@ public abstract class BaseWebflowActionTest {
flowParameters = new LocalAttributeMap<>();
when(context.getFlowScope()).thenReturn(flowParameters);
val flow = mock(Flow.class);
when(flow.getVariable("credential")).thenReturn(mock(FlowVariable.class));
when(context.getActiveFlow()).thenReturn(flow);
when(context.getRequestScope()).thenReturn(flowParameters);
when(context.getConversationScope()).thenReturn(flowParameters);
......
......@@ -5,17 +5,27 @@ import static org.mockito.Mockito.mock;
import java.util.ArrayList;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import fr.gouv.vitamui.cas.provider.ProvidersService;
import fr.gouv.vitamui.cas.util.Utils;
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.audit.AuditableExecution;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.web.cookie.CasCookieBuilder;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.web.DelegatedClientWebflowManager;
import org.apereo.cas.web.flow.SingleSignOnParticipationStrategy;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.pac4j.core.client.Clients;
import org.pac4j.core.context.session.SessionStore;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
......@@ -23,31 +33,36 @@ import org.springframework.test.context.junit4.SpringRunner;
import fr.gouv.vitamui.commons.api.identity.ServerIdentityAutoConfiguration;
/**
* Tests {@link CustomInitialFlowSetupAction}.
* Tests {@link CustomDelegatedClientAuthenticationAction}.
*
*
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ServerIdentityAutoConfiguration.class)
@TestPropertySource(locations = "classpath:/application-test.properties")
public final class CustomInitialFlowSetupActionTest extends BaseWebflowActionTest {
public final class CustomDelegatedClientAuthenticationActionTest extends BaseWebflowActionTest {
private static final String EMAIL1 = "julien@vitamui.com";
private static final String EMAIL2 = "pierre@vitamui.com";
private CustomInitialFlowSetupAction action;
private CustomDelegatedClientAuthenticationAction action;
@Override
@Before
public void setUp() {
super.setUp();
action = new CustomInitialFlowSetupAction(new ArrayList<>(), mock(ServicesManager.class),
mock(AuthenticationServiceSelectionPlan.class), mock(CasCookieBuilder.class),
mock(CasCookieBuilder.class), new CasConfigurationProperties(), mock(AuthenticationEventExecutionPlan.class),
mock(SingleSignOnParticipationStrategy.class), mock(TicketRegistrySupport.class), "", ",");
action = new CustomDelegatedClientAuthenticationAction(mock(CasDelegatingWebflowEventResolver.class),
mock(CasWebflowEventResolver.class), mock(AdaptiveAuthenticationPolicy.class),
mock(Clients.class), mock(ServicesManager.class), mock(AuditableExecution.class),
mock(DelegatedClientWebflowManager.class), mock(AuthenticationSystemSupport.class),
mock(CasConfigurationProperties.class), mock(AuthenticationServiceSelectionPlan.class),
mock(CentralAuthenticationService.class), mock(SingleSignOnParticipationStrategy.class),
mock(SessionStore.class), new ArrayList<>(), mock(IdentityProviderHelper.class),
mock(ProvidersService.class), mock(Utils.class), mock(TicketRegistry.class), "", ",");
}
@Test
public void testUsernameNoSubrogation() {
requestParameters.put("username", EMAIL1);
......
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