diff --git a/api/api-iam/iam-client/pom.xml b/api/api-iam/iam-client/pom.xml
index 476b3e6f383..5a7cb176184 100644
--- a/api/api-iam/iam-client/pom.xml
+++ b/api/api-iam/iam-client/pom.xml
@@ -14,14 +14,11 @@
-
-
21
- 17
+ 2121
- 17
- 17
+ 21
+ 21
diff --git a/api/api-iam/iam-client/src/main/java/fr/gouv/vitamui/iam/openapiclient/IamApiClient.java b/api/api-iam/iam-client/src/main/java/fr/gouv/vitamui/iam/openapiclient/IamApiClient.java
index 3c7ca670a66..97f796f31f6 100644
--- a/api/api-iam/iam-client/src/main/java/fr/gouv/vitamui/iam/openapiclient/IamApiClient.java
+++ b/api/api-iam/iam-client/src/main/java/fr/gouv/vitamui/iam/openapiclient/IamApiClient.java
@@ -29,16 +29,22 @@
import fr.gouv.vitamui.commons.api.CommonConstants;
import fr.gouv.vitamui.commons.rest.client.HttpContext;
+import fr.gouv.vitamui.commons.rest.client.HttpContextHolder;
import fr.gouv.vitamui.iam.openapiclient.invoker.ApiClient;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Supplier;
+@Slf4j
public class IamApiClient extends ApiClient {
public IamApiClient(RestTemplate restTemplate) {
@@ -52,48 +58,85 @@ protected void updateParamsForAuth(
HttpHeaders headerParams,
MultiValueMap cookieParams
) {
- final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- if (authentication != null) {
- final HttpContext context;
- if (authentication instanceof PreAuthenticatedAuthenticationToken) {
- // Needed for the initial call to usersApi.getMe() during authentication
- context = (HttpContext) authentication.getPrincipal();
- } else {
- // The other calls get normal authentication credentials
- context = (HttpContext) authentication.getCredentials();
- }
- updateParamsForAuth(headerParams, context);
- }
+ resolveContext()
+ .ifPresentOrElse(
+ context -> {
+ applyHeaders(context, headerParams);
+ log.debug("IAM headers applied. Context={}", context);
+ },
+ () -> log.warn("No HttpContext available for authNames={}", Arrays.toString(authNames))
+ );
}
- private void updateParamsForAuth(HttpHeaders headerParams, HttpContext context) {
- final Integer tenantIdentifier = context.getTenantIdentifier();
- final String userToken = context.getUserToken();
- final String applicationId = context.getApplicationId();
- final String identity = context.getIdentity();
- final String requestId = context.getRequestId();
- final String accessContractId = context.getAccessContract();
- headerParams.set(CommonConstants.X_ORIGIN_HEADER_NAME, CommonConstants.X_ORIGIN_HEADER_INTERNAL);
- if (tenantIdentifier != null) {
- headerParams.put(
- CommonConstants.X_TENANT_ID_HEADER,
- Collections.singletonList(String.valueOf(tenantIdentifier))
- );
- }
- if (userToken != null) {
- headerParams.put(CommonConstants.X_USER_TOKEN_HEADER, Collections.singletonList(userToken));
- }
- if (applicationId != null) {
- headerParams.put(CommonConstants.X_APPLICATION_ID_HEADER, Collections.singletonList(applicationId));
+ private Optional resolveContext() {
+ return resolveFromHolder().or(this::resolveFromSecurityContext);
+ }
+
+ /**
+ * First priority: context manually set in HttpContextHolder.
+ */
+ private Optional resolveFromHolder() {
+ return HttpContextHolder.get();
+ }
+
+ /**
+ * Fallback: try to resolve HttpContext from Spring Security.
+ */
+ private Optional resolveFromSecurityContext() {
+ Authentication authentication = Optional.ofNullable(SecurityContextHolder.getContext())
+ .map(SecurityContext::getAuthentication)
+ .orElse(null);
+
+ if (authentication == null) {
+ return Optional.empty();
}
- if (identity != null) {
- headerParams.put(CommonConstants.X_IDENTITY_HEADER, Collections.singletonList(identity));
+
+ // Special case: initial usersApi.getMe() call during authentication
+ if (
+ authentication instanceof PreAuthenticatedAuthenticationToken &&
+ authentication.getPrincipal() instanceof HttpContext context
+ ) {
+ return Optional.of(context);
}
- if (requestId != null) {
- headerParams.put(CommonConstants.X_REQUEST_ID_HEADER, Collections.singletonList(requestId));
+
+ // Standard case: HttpContext stored in credentials
+ if (authentication.getCredentials() instanceof HttpContext context) {
+ return Optional.of(context);
}
- if (accessContractId != null) {
- headerParams.put(CommonConstants.X_ACCESS_CONTRACT_ID_HEADER, Collections.singletonList(accessContractId));
+
+ return Optional.empty();
+ }
+
+ /**
+ * Apply IAM-related headers based on the resolved HttpContext.
+ */
+ private void applyHeaders(HttpContext context, HttpHeaders headers) {
+ headers.set(CommonConstants.X_ORIGIN_HEADER_NAME, CommonConstants.X_ORIGIN_HEADER_INTERNAL);
+
+ putIfNotNull(
+ headers,
+ CommonConstants.X_TENANT_ID_HEADER,
+ () -> Optional.ofNullable(context.getTenantIdentifier()).map(String::valueOf).orElse(null)
+ );
+
+ putIfNotNull(headers, CommonConstants.X_USER_TOKEN_HEADER, context::getUserToken);
+
+ putIfNotNull(headers, CommonConstants.X_APPLICATION_ID_HEADER, context::getApplicationId);
+
+ putIfNotNull(headers, CommonConstants.X_IDENTITY_HEADER, context::getIdentity);
+
+ putIfNotNull(headers, CommonConstants.X_REQUEST_ID_HEADER, context::getRequestId);
+
+ putIfNotNull(headers, CommonConstants.X_ACCESS_CONTRACT_ID_HEADER, context::getAccessContract);
+ }
+
+ /**
+ * Add header only if the supplied value is not null.
+ */
+ private void putIfNotNull(HttpHeaders headers, String headerName, Supplier valueSupplier) {
+ String value = valueSupplier.get();
+ if (value != null) {
+ headers.set(headerName, value);
}
}
}
diff --git a/api/api-iam/iam-client/src/main/resources/swagger.yaml b/api/api-iam/iam-client/src/main/resources/swagger.yaml
index c68215d596a..27e2328cdee 100644
--- a/api/api-iam/iam-client/src/main/resources/swagger.yaml
+++ b/api/api-iam/iam-client/src/main/resources/swagger.yaml
@@ -2526,7 +2526,7 @@ paths:
content:
'*/*':
schema:
- $ref: "#/components/schemas/UserDto"
+ $ref: "#/components/schemas/AuthUserDto"
example: null
/iam/v1/cas/subrogations:
get:
diff --git a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/IdentityProviderDto.java b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/IdentityProviderDto.java
index 91de6791792..0c0ab6a09e0 100644
--- a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/IdentityProviderDto.java
+++ b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/dto/IdentityProviderDto.java
@@ -1,38 +1,28 @@
-/**
- * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020)
- * and the signatories of the "VITAM - Accord du Contributeur" agreement.
+/*
+ * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2022)
*
- * contact@programmevitam.fr
+ * contact.vitam@culture.gouv.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 a computer program whose purpose is to implement a digital archiving back-office system managing
+ * high volumetry securely and efficiently.
*
- * 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".
+ * This software is governed by the CeCILL 2.1 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 2.1 license as
+ * circulated by CEA, CNRS and INRIA at the following URL "https://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.
+ * 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.
+ * 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.
+ * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you
+ * accept its terms.
*/
package fr.gouv.vitamui.iam.common.dto;
diff --git a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomOidcOpMetadataResolver.java b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomOidcOpMetadataResolver.java
new file mode 100644
index 00000000000..346e69a24a3
--- /dev/null
+++ b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomOidcOpMetadataResolver.java
@@ -0,0 +1,18 @@
+package fr.gouv.vitamui.iam.common.utils;
+
+import org.pac4j.oidc.config.OidcConfiguration;
+import org.pac4j.oidc.metadata.OidcOpMetadataResolver;
+import org.pac4j.oidc.profile.creator.TokenValidator;
+
+/** Custom OIDC metadata resolver. */
+public class CustomOidcOpMetadataResolver extends OidcOpMetadataResolver {
+
+ public CustomOidcOpMetadataResolver(final OidcConfiguration configuration) {
+ super(configuration);
+ }
+
+ @Override
+ protected TokenValidator createTokenValidator() {
+ return new CustomTokenValidator(configuration, this.loaded);
+ }
+}
diff --git a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidator.java b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidator.java
index 1fdf8a201ca..8af2a921e72 100644
--- a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidator.java
+++ b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidator.java
@@ -42,6 +42,7 @@
import com.nimbusds.jwt.proc.BadJWTException;
import com.nimbusds.openid.connect.sdk.Nonce;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.oidc.profile.creator.TokenValidator;
@@ -57,8 +58,11 @@ public class CustomTokenValidator extends TokenValidator {
private static final List AGENTCONNECT_ACR_VALUES = Arrays.asList("eidas1", "eidas2", "eidas3");
- public CustomTokenValidator(final OidcConfiguration configuration) {
- super(configuration);
+ private final OidcConfiguration configuration;
+
+ public CustomTokenValidator(final OidcConfiguration configuration, final OIDCProviderMetadata metadata) {
+ super(configuration, metadata);
+ this.configuration = configuration;
}
public IDTokenClaimsSet validate(final JWT idToken, final Nonce expectedNonce)
diff --git a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/IdentityProviderHelper.java b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/IdentityProviderHelper.java
index c7182445044..e6cece2ea13 100644
--- a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/IdentityProviderHelper.java
+++ b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/IdentityProviderHelper.java
@@ -102,6 +102,22 @@ public Optional findByUserIdentifierAndCustomerId(
.findFirst();
}
+ public Optional findAutoProvisioningProviderByEmail(
+ final List providers,
+ final String email
+ ) {
+ for (final IdentityProviderDto provider : providers) {
+ if (provider.isAutoProvisioningEnabled()) {
+ for (final String pattern : provider.getPatterns()) {
+ if (Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(email).matches()) {
+ return Optional.of(provider);
+ }
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
public boolean identifierMatchProviderPattern(
final List providers,
final String userEmail,
diff --git a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilder.java b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilder.java
index 281d9d47f59..d84ed93d90d 100644
--- a/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilder.java
+++ b/api/api-iam/iam-commons/src/main/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilder.java
@@ -45,6 +45,7 @@
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.pac4j.core.client.IndirectClient;
@@ -53,8 +54,6 @@
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
@@ -62,17 +61,12 @@
import java.util.Map;
import java.util.Optional;
-/**
- * A pac4j client builder.
- *
- *
- */
+/** A pac4j client builder. */
@Getter
@Setter
+@Slf4j
public class Pac4jClientBuilder {
- private static final Logger LOGGER = LoggerFactory.getLogger(Pac4jClientBuilder.class);
-
@Value("${login.url}")
@NotNull
private String casLoginUrl;
@@ -153,13 +147,14 @@ public Optional buildClient(final IdentityProviderDto provider)
oidcConfiguration.setUseNonce(useNonce != null ? useNonce : true);
final Boolean usePkce = provider.getUsePkce();
oidcConfiguration.setDisablePkce(usePkce != null ? !usePkce : true);
- oidcConfiguration.setStateGenerator((context, store) -> new Nonce().toString());
- oidcConfiguration.setTokenValidator(new CustomTokenValidator(oidcConfiguration));
+ oidcConfiguration.setStateGenerator(ctx -> new Nonce().toString());
+ oidcConfiguration.setOpMetadataResolver(new CustomOidcOpMetadataResolver(oidcConfiguration));
final OidcClient oidcClient = new OidcClient(oidcConfiguration);
setCallbackUrl(oidcClient, technicalName);
oidcClient.init();
+ oidcClient.getConfiguration().getOpMetadataResolver().load();
return Optional.of(oidcClient);
}
}
@@ -172,7 +167,7 @@ public Optional buildClient(final IdentityProviderDto provider)
} else if (message.equals("Error parsing idp Metadata")) {
throw new InvalidFormatException(message, ErrorsConstants.ERRORS_VALID_IDP_METADATA);
}
- LOGGER.error("Cannot build pac4j client with provider identifier: " + provider.getIdentifier(), e);
+ log.error("Cannot build pac4j client with provider identifier: " + provider.getIdentifier(), e);
}
return Optional.empty();
}
diff --git a/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidatorTest.java b/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidatorTest.java
index f800ecccd25..06922093610 100644
--- a/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidatorTest.java
+++ b/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/CustomTokenValidatorTest.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2022)
+ *
+ * contact.vitam@culture.gouv.fr
+ *
+ * This software is a computer program whose purpose is to implement a digital archiving back-office system managing
+ * high volumetry securely and efficiently.
+ *
+ * This software is governed by the CeCILL 2.1 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 2.1 license as
+ * circulated by CEA, CNRS and INRIA at the following URL "https://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 2.1 license and that you
+ * accept its terms.
+ */
+
package fr.gouv.vitamui.iam.common.utils;
import com.nimbusds.jose.JWSAlgorithm;
@@ -43,7 +70,6 @@ public void setUp() {
configuration = mock(OidcConfiguration.class);
final OIDCProviderMetadata metadata = mock(OIDCProviderMetadata.class);
when(metadata.getIssuer()).thenReturn(new Issuer(ISSUER));
- when(configuration.findProviderMetadata()).thenReturn(metadata);
when(configuration.getClientId()).thenReturn(CLIENT_ID);
when(configuration.getSecret()).thenReturn(CLIENT_SECRET);
when(metadata.getIDTokenJWSAlgs()).thenReturn(Arrays.asList(JWSAlgorithm.HS256));
@@ -59,7 +85,7 @@ public void setUp() {
nonce = new Nonce();
claims.put("nonce", nonce.toString());
- validator = new CustomTokenValidator(configuration);
+ validator = new CustomTokenValidator(configuration, metadata);
}
@Test
diff --git a/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilderTest.java b/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilderTest.java
index 142fadf3fe6..2eca64186e5 100644
--- a/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilderTest.java
+++ b/api/api-iam/iam-commons/src/test/java/fr/gouv/vitamui/iam/common/utils/Pac4jClientBuilderTest.java
@@ -2,7 +2,6 @@
import com.nimbusds.jose.JWSAlgorithm;
import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.pac4j.core.client.IndirectClient;
import org.pac4j.oidc.client.OidcClient;
@@ -27,7 +26,6 @@ public class Pac4jClientBuilderTest {
private static final JWSAlgorithm ALGORITHM = JWSAlgorithm.HS256;
private static final Map CUSTOM_PARAMS = Map.of("prompt", "login");
- @Disabled
@Test
public void testOidcProviderCreationSuccessful() {
final IdentityProviderDto provider = new IdentityProviderDto();
@@ -62,9 +60,8 @@ public void testOidcProviderCreationSuccessful() {
}
@Test
- public void testOidcProviderCreationFailure() {
+ public void shouldClientCreationFailsWhenNoProviderClientId() {
final IdentityProviderDto provider = new IdentityProviderDto();
- provider.setClientId(CLIENT_ID);
provider.setClientSecret(CLIENT_SECRET);
provider.setDiscoveryUrl("http://url");
@@ -72,7 +69,45 @@ public void testOidcProviderCreationFailure() {
builder.setCasLoginUrl(LOGIN_URL);
final Optional optClient = builder.buildClient(provider);
+ assertTrue(optClient.isEmpty());
+ }
+
+ @Test
+ public void shouldClientCreationFailsWhenNoProviderClientSecret() {
+ final IdentityProviderDto provider = new IdentityProviderDto();
+ provider.setClientId(CLIENT_ID);
+ provider.setDiscoveryUrl("http://url");
+
+ final Pac4jClientBuilder builder = new Pac4jClientBuilder();
+ builder.setCasLoginUrl(LOGIN_URL);
+
+ final Optional optClient = builder.buildClient(provider);
+ assertTrue(optClient.isEmpty());
+ }
+
+ @Test
+ public void shouldClientCreationFailsWhenNoProviderDiscoveryUrl() {
+ final IdentityProviderDto provider = new IdentityProviderDto();
+ provider.setClientId(CLIENT_ID);
+ provider.setClientSecret(CLIENT_SECRET);
+
+ final Pac4jClientBuilder builder = new Pac4jClientBuilder();
+ builder.setCasLoginUrl(LOGIN_URL);
+
+ final Optional optClient = builder.buildClient(provider);
+ assertTrue(optClient.isEmpty());
+ }
+ @Test
+ public void shouldClientCreationFailsWhenNoBuilderLoginUrl() {
+ final IdentityProviderDto provider = new IdentityProviderDto();
+ provider.setClientId(CLIENT_ID);
+ provider.setClientSecret(CLIENT_SECRET);
+ provider.setDiscoveryUrl("http://url");
+
+ final Pac4jClientBuilder builder = new Pac4jClientBuilder();
+
+ final Optional optClient = builder.buildClient(provider);
assertTrue(optClient.isEmpty());
}
}
diff --git a/api/api-iam/iam/pom.xml b/api/api-iam/iam/pom.xml
index d91123c6cdd..882bccd7df2 100644
--- a/api/api-iam/iam/pom.xml
+++ b/api/api-iam/iam/pom.xml
@@ -23,6 +23,10 @@
+
+ fr.gouv.vitamui.commons
+ commons-utils
+ fr.gouv.vitamuiiam-commons
diff --git a/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/cas/service/CasService.java b/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/cas/service/CasService.java
index 73fac8ce2fb..591ab114597 100644
--- a/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/cas/service/CasService.java
+++ b/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/cas/service/CasService.java
@@ -82,8 +82,6 @@
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
-import org.apereo.cas.ticket.UniqueTicketIdGenerator;
-import org.apereo.cas.util.DefaultUniqueTicketIdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -107,6 +105,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
/**
@@ -202,7 +201,16 @@ public class CasService {
@SuppressWarnings("unused")
private static final Logger LOGGER = LoggerFactory.getLogger(CasService.class);
- private static final UniqueTicketIdGenerator TICKET_GENERATOR = new DefaultUniqueTicketIdGenerator();
+ /**
+ * Generate a unique ticket ID with the given prefix.
+ * Uses UUID for guaranteed uniqueness.
+ *
+ * @param prefix The prefix for the ticket ID
+ * @return A unique ticket ID in the format: prefix-uuid
+ */
+ private static String generateUniqueTicketId(String prefix) {
+ return prefix + "-" + UUID.randomUUID().toString();
+ }
public CasService() {}
@@ -346,10 +354,10 @@ private UserDto loadFullUserProfileIfRequired(
/**
* Method to retrieve the user information
*
- * @param loginEmail email of the user
+ * @param loginEmail email of the user
* @param loginCustomerId The customerId of the user
- * @param idp can be null
- * @param userIdentifier can be null
+ * @param idp can be null
+ * @param userIdentifier can be null
* @param optEmbedded
* @return
*/
@@ -637,7 +645,7 @@ private void generateAndAddAuthToken(final AuthUserDto user, final boolean isSub
token.setCreatedDate(currentDate);
final Date nowPlusXMinutes = DateUtils.addMinutes(currentDate, ttlInMinutes);
token.setUpdatedDate(nowPlusXMinutes);
- token.setId(TICKET_GENERATOR.getNewTicketId(TOKEN_PREFIX));
+ token.setId(generateUniqueTicketId(TOKEN_PREFIX));
token.setSurrogation(isSubrogation);
tokenRepository.save(token);
user.setLastConnection(OffsetDateTime.now());
diff --git a/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/customer/config/CustomerInitConfig.java b/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/customer/config/CustomerInitConfig.java
index 4d40d0a6387..d4d487d3d11 100644
--- a/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/customer/config/CustomerInitConfig.java
+++ b/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/customer/config/CustomerInitConfig.java
@@ -39,7 +39,6 @@
import fr.gouv.vitamui.commons.api.domain.Role;
import fr.gouv.vitamui.commons.api.domain.ServicesData;
-import fr.gouv.vitamui.commons.spring.YamlPropertySourceFactory;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@@ -60,7 +59,10 @@
@Getter
@Setter
@Component
-@PropertySource(factory = YamlPropertySourceFactory.class, value = "file:${customer.init.config.file}")
+@PropertySource(
+ factory = fr.gouv.vitamui.commons.spring.YamlPropertySourceFactory.class,
+ value = "file:${customer.init.config.file}"
+)
@ConfigurationProperties("customer-init")
public class CustomerInitConfig implements InitializingBean {
diff --git a/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/security/IamUserAuthentificationService.java b/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/security/IamUserAuthentificationService.java
index f368f99078b..829d46ae27d 100644
--- a/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/security/IamUserAuthentificationService.java
+++ b/api/api-iam/iam/src/main/java/fr/gouv/vitamui/iam/server/security/IamUserAuthentificationService.java
@@ -77,7 +77,7 @@ public class IamUserAuthentificationService implements UserAuthenticationService
@NotNull
private Integer tokenAdditionalTtl;
- @Value("${cas.secret.token}")
+ @Value("${cas_secret_token}")
@NotNull
private String casSecretToken;
diff --git a/api/api-iam/iam/src/main/resources/application-dev.yml b/api/api-iam/iam/src/main/resources/application-dev.yml
index bb9dcb0c54c..407c800d567 100644
--- a/api/api-iam/iam/src/main/resources/application-dev.yml
+++ b/api/api-iam/iam/src/main/resources/application-dev.yml
@@ -33,6 +33,15 @@ server:
client-certificate-header-name: x-ssl-cert
error:
path: /error
+ tomcat:
+ connection-timeout: 60000
+ accesslog:
+ enabled: true
+ max-days: 365
+ directory: "/vitamui/log/iam"
+ prefix: "accesslog-iam"
+ file-date-format: ".yyyy-MM-dd"
+ suffix: ".log"
management:
server:
@@ -62,7 +71,7 @@ cas-client:
cas.reset.password.url: /cas/extras/resetPassword?username={username}&firstname={firstname}&lastname={lastname}&language={language}&customerId={customerId}&ttl=1day
cas.tenant.identifier: -1
-cas.secret.token: tokcas_ie6UZsEcHIWrfv2x
+cas_secret_token: tokcas_ie6UZsEcHIWrfv2x
login:
url: http://dev.vitamui.com:8080/cas/login
diff --git a/api/api-iam/iam/src/test/resources/application.yml b/api/api-iam/iam/src/test/resources/application.yml
index 6f922211108..437e5d3af9d 100644
--- a/api/api-iam/iam/src/test/resources/application.yml
+++ b/api/api-iam/iam/src/test/resources/application.yml
@@ -42,7 +42,7 @@ customer.init.config.file: src/test/resources/customer-init.yml
cas.reset.password.url: /extras/resetPassword?username={username}&firstname={firstname}&lastname={lastname}&language={language}&customerId={customerId}&ttl=1day
cas.tenant.identifier: -1
-cas.secret.token: tokcas_ie6UZsEcHIWrfv2x
+cas_secret_token: tokcas_ie6UZsEcHIWrfv2x
address:
max-street-length: 250
diff --git a/bom/pom.xml b/bom/pom.xml
index 777f93c1f94..7958c6ef3c1 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -18,7 +18,7 @@
2.0.313.25.31.78
- 6.6.12
+ 7.0.10.11.9.44.41.26.1
@@ -33,14 +33,14 @@
2.3.303.0.22.10.1
- 6.1.7.Final
+ 8.0.1.Final5.4.45.3.44.0.0-M22.17.03.3.15.0.1
- 1.6.5
+ 2.0.14.0.23.30.2-GA6.0.0
@@ -54,27 +54,24 @@
1.2.3202403031.5.0
- 1.1.1
- 1.2.13
+ 1.4.141.18.381.3.0.Final1.13.15
- 4.6.11.1.05.40.8.2-incubating0.8.7
- 5.4.6
+ 6.1.17.2.5.RELEASE5.2.51.03.3.102.2.63.3.10
- 1.7.30
+ 2.0.92025.0.06.2.8
- 6.0.33.0.01.21.420220510
@@ -313,11 +310,6 @@
-
- org.pac4j
- spring-webmvc-pac4j
- ${spring-webmvc-pac4j.version}
- org.pac4jpac4j-saml
@@ -343,36 +335,6 @@
pac4j-jwt${pac4j.version}
-
- org.pac4j
- pac4j-jee
- ${pac4j.version}
-
-
- org.pac4j
- pac4j-javaee
- ${pac4j.version}
-
-
- org.pac4j
- pac4j-http
- ${pac4j.version}
-
-
- org.pac4j
- pac4j-config
- ${pac4j.version}
-
-
- org.pac4j
- pac4j-cas
- ${pac4j.version}
-
-
- org.pac4j
- pac4j-oauth
- ${pac4j.version}
- org.pac4jpac4j-core
@@ -682,11 +644,6 @@
commons-codec${apache.commons.codec.version}
-
- org.gandon.tomcat
- juli-to-slf4j
- ${juli.to.slf4j.version}
- com.fasterxml.jackson.dataformat
diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml
index ed806a9cdf3..e229f0317ef 100644
--- a/cas/cas-server/pom.xml
+++ b/cas/cas-server/pom.xml
@@ -11,59 +11,126 @@
VITAMUI CAS Server
+
UTF-8UTF-8
-
- 17
- 17
- 17
- 17
- 17
+ 21
+ 21
+ 21
- 6.6.12
+
+ 3.2.1
+ 4.1.0
-
- 3.11.1
+
+ 7.0.10.1
+ 6.1.1
+ 6.0.3
+
+
+ 2.0.9
+ 1.4.14
+ 8.0
+ 2.16.1
+ 1.18.36
+ 2.0.1
+ 4.0.17
+ 3.6.2
+ 1.12.1
+ 2.2.26.5.1
- true
+ 1.14.11.13.2
+ 8.0.1.Final
+ 2.15.1
+ 1.8.20.GA
+
+
5.7.2
- 1.9.35.2.0
- 4.7.1
- ${project.build.finalName}.war
- false
- 6.0.3
- 2.7.18
- 3.1.1
- 5.3.22
- 5.3.22
- 2.2.2
- 3.0.15.RELEASE
+ 3.11.1
-
+
+ 3.11.0
+ 3.2.03.4.20.3.1
- 3.2.02.43.03.2.42.6.0
+
+
+ true
+ false
+ ${project.build.finalName}.war
+
fr.gouv.vitamuibom
- 8.1.1
+ ${project.version}
+ pom
+ import
+
+
+
+
+ org.apereo.cas
+ cas-server-support-bom
+ ${cas.version}pomimport
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+ org.apache.groovy
+ groovy-bom
+ ${groovy.version}
+ pom
+ import
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ ${jackson.version}
+ pom
+ import
+
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.2.3
+
+
+ org.apache.httpcomponents.core5
+ httpcore5
+ 5.2.4
+
+
+ org.apache.httpcomponents.core5
+ httpcore5-h2
+ 5.2.4
+
-
+
+
+
fr.gouv.vitamuiiam-client
@@ -91,39 +158,49 @@
-
+
+
+
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+ org.springframework.bootspring-boot-autoconfigure
- ${spring.boot.version}provided
-
-
org.springframework.cloudspring-cloud-starter-consul-discovery
- ${spring.cloud.consul.version}runtimespring-cloud-starter-loadbalancerorg.springframework.cloud
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpcore
+ org.springframework.cloudspring-cloud-commons
- ${spring.cloud.consul.version}org.springframework.cloudspring-cloud-context
- ${spring.cloud.consul.version}
-
+
+
+
org.apereo.cascas-server-webapp-tomcat
@@ -131,13 +208,27 @@
warruntime
+
+ org.apereo.cas
+ cas-server-webapp-init
+
+
+ org.apereo.cas
+ cas-server-core
+
-
+
+
+
+
org.apereo.cascas-server-support-mongo-service-registry
- ${cas.version}
+
+ org.apache.httpcomponents.core5
+ httpcore5
+ io.dropwizard.metricsmetrics-core
@@ -149,360 +240,362 @@
- org.mongodb
- mongodb-driver-sync
- ${mongo.version}
+ org.apereo.cas
+ cas-server-core-services-api
+
+
+ org.apereo.cas
+ cas-server-core-services-authentication
+
+
+ org.apereo.cas
+ cas-server-core-services
-
+
org.apereo.cas
- cas-server-core-authentication
- ${cas.version}
+ cas-server-support-hazelcast-ticket-registryorg.apereo.cas
- cas-server-support-pac4j-webflow
- ${cas.version}
+ cas-server-core-tickets
+
+
+
+
+ org.apereo.cas
+ cas-server-core-authentication
- io.dropwizard.metrics
- metrics-core
+ org.apereo.inspektr
+ inspektr-audit
+
+
+ org.apereo.inspektr
+ inspektr-common
+
+
+ org.apereo.inspektr
+ inspektr-support-springorg.apereo.cas
- cas-server-support-pac4j-core
- ${cas.version}
+ cas-server-core-authentication-apiorg.apereo.cascas-server-support-pac4j-api
- ${cas.version}org.apereo.cas
- cas-server-support-pac4j-core-clients
- ${cas.version}
+ cas-server-support-pac4j-coreorg.apereo.cas
- cas-server-core-web
- ${cas.version}
+ cas-server-support-pac4j-core-clientsorg.apereo.cas
- cas-server-core-util
- ${cas.version}
+ cas-server-support-pac4j-webflow
+
+
org.apereo.cascas-server-support-saml-core-api
- ${cas.version}
- org.pac4j
- pac4j-jee
-
-
- org.pac4j
- pac4j-javaee
-
-
- org.pac4j
- pac4j-http
+ org.apereo.cas
+ cas-server-support-saml-core
- org.pac4j
- pac4j-config
+ org.apereo.cas
+ cas-server-support-oauth-webflow
- org.pac4j
- pac4j-cas
+ org.apereo.cas
+ cas-server-support-oauth
- org.pac4j
- pac4j-oauth
+ org.apereo.cas
+ cas-server-support-oauth-api
- org.pac4j
- pac4j-core
+ org.apereo.cas
+ cas-server-support-oauth-core
- org.pac4j
- spring-webmvc-pac4j
+ org.apereo.cas
+ cas-server-support-oauth-core-api
- org.pac4j
- pac4j-saml
+ org.apereo.cas
+ cas-server-support-oauth-services
- org.pac4j
- pac4j-oidc
+ org.apereo.cas
+ cas-server-support-oidc
- javax.servlet
- javax.servlet-api
- provided
+ org.apereo.cas
+ cas-server-support-oidc-core-api
-
-
org.apereo.cas
- cas-server-support-hazelcast-ticket-registry
- ${cas.version}
+ cas-server-support-oidc-core
- commons-io
- commons-io
+ org.apereo.cas
+ cas-server-support-token-core-api
-
+
org.apereo.cascas-server-support-x509-webflow
- ${cas.version}org.apereo.cascas-server-support-x509-core
- ${cas.version}
-
-
- org.apereo.cas
- cas-server-core-webflow-api
- ${cas.version}
-
+
org.apereo.cascas-server-support-surrogate-api
- ${cas.version}org.apereo.cascas-server-support-surrogate-authentication
- ${cas.version}org.apereo.cascas-server-support-surrogate-webflow
- ${cas.version}org.apereo.cas
- cas-server-core-services-api
- ${cas.version}
+ cas-server-support-surrogate-core
-
+
org.apereo.cascas-server-support-pm-webflow
- ${cas.version}
-
-
- org.apereo.cas
- cas-server-core
- ${cas.version}org.apereo.cascas-server-support-pm-core
- ${cas.version}
-
-
- org.apereo.cas
- cas-server-core-notifications
- ${cas.version}
-
+
org.apereo.cascas-server-support-simple-mfa
- ${cas.version}org.apereo.cascas-server-support-simple-mfa-core
- ${cas.version}org.apereo.cascas-server-core-authentication-mfa
- ${cas.version}
+
+
+ org.apereo.cas
+ cas-server-core-authentication-mfa-apiorg.apereo.cascas-server-core-webflow-mfa-api
- ${cas.version}org.apereo.cascas-server-support-sms-smsmode
- ${cas.version}
+
+
org.apereo.cas
- cas-server-core-authentication-mfa-api
- ${cas.version}
+ cas-server-core-webflow-apiorg.apereo.cas
- cas-server-support-bucket4j-core
- ${cas.version}
+ cas-server-core-web-api
-
-
org.apereo.cas
- cas-server-support-throttle
- ${cas.version}
+ cas-server-core-web
-
-
org.apereo.cas
- cas-server-support-actions-core
- ${cas.version}
+ cas-server-core-cookie-api
-
-
org.apereo.cas
- cas-server-core-cookie-api
- ${cas.version}
+ cas-server-core-utilorg.apereo.cas
- cas-server-core-web-api
- ${cas.version}
+ cas-server-core-notificationsorg.apereo.cas
- cas-server-core-authentication-api
- ${cas.version}
+ cas-server-core-notifications-apiorg.apereo.cas
- cas-server-support-actions
- ${cas.version}
+ cas-server-support-actions-coreorg.apereo.cas
- cas-server-webapp-init
- ${cas.version}
+ cas-server-support-actionsorg.apereo.cas
- cas-server-core-tickets
- ${cas.version}
+ cas-server-support-throttleorg.apereo.cas
- cas-server-core-services-authentication
- ${cas.version}
+ cas-server-support-metricsorg.apereo.cas
- cas-server-support-saml-core
- ${cas.version}
+ cas-server-support-webconfig
- io.opentracing.contrib
- opentracing-spring-jaeger-web-starter
+ org.apereo.cas
+ cas-server-support-bucket4j-core
- com.sun.mail
- jakarta.mail
+ org.apereo.cas
+ cas-server-support-passwordless-api
-
+
+
+
- org.apereo.cas
- cas-server-support-oauth-webflow
- ${cas.version}
+ org.pac4j
+ pac4j-javaee
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-oauth
- ${cas.version}
+ org.pac4j
+ pac4j-http
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-oauth-api
- ${cas.version}
+ org.pac4j
+ pac4j-config
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-oauth-core
- ${cas.version}
+ org.pac4j
+ pac4j-cas
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-token-core-api
- ${cas.version}
+ org.pac4j
+ pac4j-oauth
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-oauth-core-api
- ${cas.version}
+ org.pac4j
+ pac4j-core
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-oauth-services
- ${cas.version}
+ org.pac4j
+ pac4j-saml
+ ${pac4j.version}
-
-
- org.apereo.cas
- cas-server-support-oidc
- ${cas.version}
+ org.pac4j
+ pac4j-oidc
+ ${pac4j.version}
- org.apereo.cas
- cas-server-support-oidc-core-api
- ${cas.version}
+ org.pac4j
+ spring-webmvc-pac4j
+ ${spring-webmvc-pac4j.version}
- org.apereo.cas
- cas-server-support-oidc-core
- ${cas.version}
+ jakarta.servlet
+ jakarta.servlet-api
+ provided
-
+
+
+
+
- org.apereo.cas
- cas-server-webapp-config
- ${cas.version}
+ com.fasterxml.jackson.core
+ jackson-annotations
- org.apereo.cas
- cas-server-core-services
- ${cas.version}
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
-
+
- org.apereo.cas
- cas-server-support-metrics
- ${cas.version}
+ io.projectreactor
+ reactor-core
+
+
+
+ org.springframework.data
+ spring-data-mongodb
+
+
+ org.mongodb
+ mongodb-driver-sync
+
+
+
io.micrometermicrometer-registry-prometheus
- ${micrometer.version}
+
+
+ io.micrometer
+ micrometer-tracing-bridge-otel
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
-
+
+
+ org.apereo.cas
+ cas-server-support-logback
+
+
+ org.codehaus.janino
+ janino
+ ch.qos.logbacklogback-classic
+
+ ch.qos.logback
+ logback-core
+
+
+ org.slf4j
+ slf4j-api
+ org.slf4jjcl-over-slf4j
@@ -512,25 +605,35 @@
jul-to-slf4j
- org.gandon.tomcat
- juli-to-slf4j
+ net.logstash.logback
+ logstash-logback-encoder
+ ${logstash.logback.encoder.version}
-
+
org.projectlomboklombok
- 1.18.38
+ provided
- org.thymeleaf
- thymeleaf-spring5
- ${thymeleaf-spring5.version}
+ org.hibernate.validator
+ hibernate-validator
+ ${hibernate-validator.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}commons-codeccommons-codec
+
+ org.eclipse.angus
+ jakarta.mail
+ org.webjarsjquery-ui
@@ -542,34 +645,54 @@
${font-awesome.version}
-
+
+
+
org.junit.vintagejunit-vintage-engine
- ${junit-vintage-engine.version}testorg.mockitomockito-inline
- ${mockito.version}testorg.springframeworkspring-test
- ${spring.test.version}testorg.assertjassertj-core
- ${assertj-core.version}test
+
+
+
+ org.apereo.inspektr
+ inspektr-common
+ ${inspektr.version}
+ provided
+
+
+ org.apereo.inspektr
+ inspektr-audit
+ ${inspektr.version}
+ provided
+
+
+ org.apereo.inspektr
+ inspektr-support-spring
+ ${inspektr.version}
+ provided
+
+ ${project.artifactId}
+
src/main/resources
@@ -580,10 +703,9 @@
- ${project.artifactId}
-
+
com.diffplug.spotlessspotless-maven-plugin
@@ -597,8 +719,7 @@
${spotless-maven-plugin.prettier.version}
- ${spotless-maven-plugin.prettier-plugin-java.version}
-
+ ${spotless-maven-plugin.prettier-plugin-java.version}120
@@ -625,6 +746,22 @@
+
+
+ maven-compiler-plugin
+ ${maven.compiler.plugin.version}
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+
org.apache.maven.pluginsmaven-war-plugin
@@ -634,9 +771,7 @@
falsefalse
-
- ${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp-tomcat/META-INF/MANIFEST.MF
-
+ ${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp-tomcat/META-INF/MANIFEST.MF
@@ -661,10 +796,16 @@
WEB-INF/lib/oauth2-oidc-sdk-*.jarWEB-INF/lib/pac4j-*.jarWEB-INF/lib/slf4j-api-*.jar
- WEB-INF/lib/slf4j-api-*.jarWEB-INF/lib/spring-boot-starter-log4j2-*.jarWEB-INF/lib/spring-expression-*.jarWEB-INF/lib/spring-webmvc-pac4j-*.jar
+ WEB-INF/lib/log4j-slf4j2-impl-*.jar
+ WEB-INF/lib/log4j-jakarta-web-*.jar
+ WEB-INF/lib/log4j-spring-boot-*.jar
+ WEB-INF/lib/log4j-spring-cloud-config-client-*.jar
+ WEB-INF/lib/httpclient5-*.jar
+ WEB-INF/lib/httpclient-*.jar
+ WEB-INF/lib/httpcore-*.jar
@@ -689,11 +830,19 @@
WEB-INF/lib/slf4j-api-*.jar,
WEB-INF/lib/oauth2-oidc-sdk-*.jar,
WEB-INF/lib/pac4j-*.jar,
- WEB-INF/lib/spring-webmvc-pac4j-*.jar
+ WEB-INF/lib/spring-webmvc-pac4j-*.jar,
+ WEB-INF/lib/log4j-slf4j2-impl-*.jar,
+ WEB-INF/lib/log4j-jakarta-web-*.jar,
+ WEB-INF/lib/log4j-spring-boot-*.jar,
+ WEB-INF/lib/log4j-spring-cloud-config-client-*.jar,
+ WEB-INF/lib/httpclient5-*.jar,
+ WEB-INF/lib/httpclient-*.jar,
+ WEB-INF/lib/httpcore-*.jar
+
org.springframework.bootspring-boot-maven-plugin
@@ -707,9 +856,7 @@
-
- --spring.config.additional-location=file:${basedir}/src/main/config/cas-server-application-dev.yml
-
+ --spring.config.additional-location=file:${basedir}/src/main/config/cas-server-application-dev.yml
@@ -721,6 +868,7 @@
+
com.gitlab.hayneslibsass-maven-plugin
@@ -737,9 +885,11 @@
${project.basedir}/src/main/config/sass${project.basedir}/src/main/resources/static/cssfalse
-
+ compressed
+
+
com.google.cloud.toolsjib-maven-plugin
@@ -766,7 +916,7 @@
-
+
rpm
@@ -801,7 +951,7 @@
-
+
deb
@@ -827,9 +977,6 @@
NAME=${project.artifactId}VERSION=${project.version}JAR_FILE=${rpm.jar-file}
-
DEPENDENCIES=systemd
diff --git a/cas/cas-server/src/main/config/cas-server-application-dev.yml b/cas/cas-server/src/main/config/cas-server-application-dev.yml
index 05b02d78d3c..5d7366090d4 100644
--- a/cas/cas-server/src/main/config/cas-server-application-dev.yml
+++ b/cas/cas-server/src/main/config/cas-server-application-dev.yml
@@ -38,8 +38,14 @@ management:
port: 7080
ssl:
enabled: false
-#management.metrics.export.prometheus.enabled: true
-
+ elastic:
+ metrics:
+ export:
+ enabled: false
+ prometheus:
+ metrics:
+ export:
+ enabled: false
vitamui.cas.tenant.identifier: -1
vitamui.cas.identity: cas
@@ -78,19 +84,29 @@ cas.authn.oauth.crypto.signing.key: kSs5OT5bTV6E9Ba0biGZ3taVlmlBFmoMyvG4JB0pSiZJ
cas.authn.oauth.access-token.crypto.encryption.key: RGAYHpTTKJ-YMbh7Yrt3Xd6VQH_myXYISwThfo-9OKI
cas.authn.oauth.access-token.crypto.signing.key: j8AZtUo6i4BI2n3bu2Elr9d3aIOL35vec0AjUBubP6rgj5arKWB9lRcWq9bjxGIwoAbFv-1MRmiScXlIIX0BGg
+cas.authn.oauth.session-replication.cookie.crypto.encryption.key: 2macKckIM22PrUn9cHkyVe1lB4jc12hXsbUa8e7Ih8Q
+cas.authn.oauth.session-replication.cookie.crypto.signing.key: vhnibDmTxFA0q_jO3XedyoHgKkPQPmfVeWLVj9byRfLiVFqZi38ZsEy4Qt0Vu2rZFXceBgp5nb55iWgxo2EzCQ
+cas.authn.pac4j:
+ core.session-replication.cookie:
+ name: DISSESSIONAD
+ crypto:
+ encryption.key: T4LxS93IaaD-F-7kz66VEc2BD3g8uGGYKPECFkV9vBU
+ signing.key: fRYHy2_3_qApascjXEaXuTL5o52nrohztttpiGKFkWpXYUHaV85lS8Ph10OGfkpUUZlkyhhq6oPOXOlGIAQbiQ
+ cas:
+ - login-url: https://dev.vitamui.com:8080/cas/login
+
cas.server.prefix: https://dev.vitamui.com:8080/cas
login.url: ${cas.server.prefix}/login
-cas.service-registry.mongo.client-uri: mongodb://mongod_dbuser_cas:mongod_dbpwd_cas@localhost:27018/cas
-
-#cas.service-registry.mongo.port: 27018
-#cas.service-registry.mongo.database-name: cas
-#cas.service-registry.mongo.authentication-database-name: cas
-#cas.service-registry.mongo.replica-set: rs0
-cas.service-registry.mongo.collection: services
-#cas.service-registry.mongo.user-id: mongod_dbuser_cas
-#cas.service-registry.mongo.password: mongod_dbpwd_cas
-
+cas.service-registry.mongo:
+ client-uri: mongodb://mongod_dbuser_cas:mongod_dbpwd_cas@localhost:27018/cas
+# port: 27018
+# database-name: cas
+# authentication-database-name: cas
+# replica-set: rs0
+ collection: services
+# user-id: mongod_dbuser_cas
+# password: mongod_dbpwd_cas
cas.authn.surrogate.separator: ","
cas.authn.surrogate.mail.attribute-name: fakeNameToBeSureToFindNoAttributeAndNeverSendAnEmail
@@ -155,7 +171,7 @@ cas.sms-provider.sms-mode.access-token: changeme
vitamui.portal.url: https://dev.vitamui.com:4200/
-cas.secret.token: tokcas_ie6UZsEcHIWrfv2x
+cas_secret_token: tokcas_ie6UZsEcHIWrfv2x
ip.header: X-Real-IP
@@ -187,6 +203,8 @@ logging:
org.springframework.context.annotation: 'OFF'
org.springframework.boot.devtools: 'OFF'
org.apereo.inspektr.audit.support: 'INFO'
+ org.springframework.webflow: INFO
+ org.apereo: INFO
# Cas CORS (necessary for mobile app)
cas.http-web-request.cors.enabled: true
@@ -206,24 +224,34 @@ password:
defaults:
fr:
messages:
- - minimum ${password.length} caractères
+ - Avoir une taille d'au moins ${password.length} caractères
special-chars:
- title: 'minimum 2 caractères issus de chaque catégorie, pour au moins 3 des catégories suivantes :'
+ title: 'Contenir au moins 2 caractères issus de chaque catégorie, pour au moins 3 des catégories suivantes:'
messages:
- - minuscules (a-z)
- - majuscules (A-Z)
- - numériques (0-9)
- - caractères spéciaux (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
+ - Minuscules (a-z)
+ - Majuscules (A-Z)
+ - Numériques (0-9)
+ - Caractères spéciaux (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
en:
messages:
- - minimum ${password.length} characters
+ - Have a size of at least ${password.length} characters
special-chars:
- title: 'minimum 2 characters from each category, for at least 3 of the following categories :'
+ title: 'Contain at least 2 characters from each category, for at least 3 of the following categories:'
messages:
- - lowercases (a-z)
- - uppercases (A-Z)
- - digital (0-9)
- - special characters (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
+ - Uppercases (a-z)
+ - Lowercases (A-Z)
+ - Digits (0-9)
+ - Special Characters (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
+ de:
+ messages:
+ - Mindestens ${password.length} Zeichen lang sein
+ special-chars:
+ title: 'Mindestens 2 Zeichen aus jeder Kategorie enthalten, für mindestens 3 der folgenden Kategorien:'
+ messages:
+ - Großbuchstaben (a-z)
+ - Kleinbuchstaben (A-Z)
+ - Ziffern (0-9)
+ - Spezielle Charaktere (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
customs:
fr:
title: 'Pour des raisons de sécurité, votre mot de passe doit:'
@@ -237,7 +265,12 @@ password:
- At least ${password.length} characters
- Lowercase and uppercase
- At least one number and one special character (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
-
+ de:
+ title: 'Aus Sicherheitsgründen muss Ihr Passwort:'
+ messages:
+ - Mindestens ${password.length} Zeichen
+ - Klein- und Großbuchstaben
+ - Mindestens eine Zahl und ein Sonderzeichen (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
---
spring:
diff --git a/cas/cas-server/src/main/config/cas-server-application-recette.yml b/cas/cas-server/src/main/config/cas-server-application-recette.yml
index 736a950ba38..ce0f8130a79 100644
--- a/cas/cas-server/src/main/config/cas-server-application-recette.yml
+++ b/cas/cas-server/src/main/config/cas-server-application-recette.yml
@@ -126,7 +126,7 @@ cas.sms-provider.sms-mode.access-token: changeme
vitamui.portal.url: https://dev.vitamui.com:9000/
-cas.secret.token: tokcas_ie6UZsEcHIWrfv2x
+cas_secret_token: tokcas_ie6UZsEcHIWrfv2x
ip.header: X-Real-IP
@@ -188,7 +188,7 @@ password:
messages:
- lowercases (a-z)
- uppercases (A-Z)
- - digital (0-9)
+ - digits (0-9)
- special characters (!"#$%&£'()*+,-./:;<=>?@[]^_`{|}~)
customs:
fr:
diff --git a/cas/cas-server/src/main/config/sass/_login.scss b/cas/cas-server/src/main/config/sass/_login.scss
index 24a6ae23d55..a342bd29287 100644
--- a/cas/cas-server/src/main/config/sass/_login.scss
+++ b/cas/cas-server/src/main/config/sass/_login.scss
@@ -1,6 +1,5 @@
.login {
-
position: fixed;
height: 100%;
top: 0;
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandler.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/LoginPwdAuthenticationHandler.java
similarity index 55%
rename from cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandler.java
rename to cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/LoginPwdAuthenticationHandler.java
index 6f47c26353b..de5175329a6 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserAuthenticationHandler.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/LoginPwdAuthenticationHandler.java
@@ -36,8 +36,6 @@
*/
package fr.gouv.vitamui.cas.authentication;
-import fr.gouv.vitamui.cas.util.Constants;
-import fr.gouv.vitamui.cas.util.Utils;
import fr.gouv.vitamui.commons.api.domain.UserDto;
import fr.gouv.vitamui.commons.api.enums.UserStatusEnum;
import fr.gouv.vitamui.commons.api.enums.UserTypeEnum;
@@ -45,8 +43,10 @@
import fr.gouv.vitamui.commons.api.exception.InvalidFormatException;
import fr.gouv.vitamui.commons.api.exception.TooManyRequestsException;
import fr.gouv.vitamui.commons.api.exception.VitamUIException;
-import fr.gouv.vitamui.iam.client.CasRestClient;
-import lombok.val;
+import fr.gouv.vitamui.iam.openapiclient.CasApi;
+import fr.gouv.vitamui.iam.openapiclient.domain.LoginRequestDto;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
@@ -56,15 +56,13 @@
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.CredentialNotFoundException;
-import javax.servlet.http.HttpServletRequest;
import java.security.GeneralSecurityException;
import java.time.OffsetDateTime;
import java.util.ArrayList;
@@ -72,29 +70,29 @@
import java.util.List;
import java.util.Map;
+import static fr.gouv.vitamui.cas.util.Constants.FLOW_LOGIN_CUSTOMER_ID;
+import static fr.gouv.vitamui.cas.util.Constants.FLOW_LOGIN_EMAIL;
+import static fr.gouv.vitamui.cas.util.Constants.FLOW_SURROGATE_CUSTOMER_ID;
+import static fr.gouv.vitamui.cas.util.Constants.FLOW_SURROGATE_EMAIL;
+
/**
* Authentication handler to check the username/password on the IAM API.
*/
-public class UserAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(UserAuthenticationHandler.class);
-
- private final CasRestClient casRestClient;
+@Slf4j
+public class LoginPwdAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
- private final Utils utils;
+ private final CasApi casApi;
private final String ipHeaderName;
- public UserAuthenticationHandler(
+ public LoginPwdAuthenticationHandler(
final ServicesManager servicesManager,
final PrincipalFactory principalFactory,
- final CasRestClient casRestClient,
- final Utils utils,
+ final CasApi casApi,
final String ipHeaderName
) {
- super(UserAuthenticationHandler.class.getSimpleName(), servicesManager, principalFactory, 1);
- this.casRestClient = casRestClient;
- this.utils = utils;
+ super(LoginPwdAuthenticationHandler.class.getSimpleName(), servicesManager, principalFactory, 1);
+ this.casApi = casApi;
this.ipHeaderName = ipHeaderName;
}
@@ -103,79 +101,132 @@ protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInter
final UsernamePasswordCredential transformedCredential,
final String originalPassword
) throws GeneralSecurityException, PreventedException {
- val requestContext = RequestContextHolder.getRequestContext();
- val flowScope = requestContext.getFlowScope();
- val loginEmail = flowScope.getRequiredString(Constants.FLOW_LOGIN_EMAIL);
- val loginCustomerId = flowScope.getRequiredString(Constants.FLOW_LOGIN_CUSTOMER_ID);
- val surrogateEmail = flowScope.getString(Constants.FLOW_SURROGATE_EMAIL);
- val surrogateCustomerId = flowScope.getString(Constants.FLOW_SURROGATE_CUSTOMER_ID);
- val externalContext = requestContext.getExternalContext();
- val ip = ((HttpServletRequest) externalContext.getNativeRequest()).getHeader(ipHeaderName);
- val context = utils.buildContext(loginEmail);
-
- LOGGER.debug(
- "Authenticating loginEmail: {} / loginCustomerId: {} / surrogateEmail: {} / surrogateCustomerId:" +
- " {} / IP: {}",
- loginEmail,
- loginCustomerId,
- surrogateEmail,
- surrogateCustomerId,
- ip
- );
+ final var login = buildLoginRequestFromFlowScopeData(originalPassword);
try {
- val user = casRestClient.login(
- context,
- loginEmail,
- loginCustomerId,
- originalPassword,
- surrogateEmail,
- surrogateCustomerId,
- ip
- );
+ final var user = casApi.login(login);
+
if (user != null) {
if (mustChangePassword(user)) {
- LOGGER.info("Password expired for: {} ({})", loginEmail, loginCustomerId);
- throw new AccountPasswordMustChangeException("Password expired for: " + loginEmail);
+ LOGGER.info("Password expired for: {} ({})", login.getLoginEmail(), login.getLoginCustomerId());
+ throw new AccountPasswordMustChangeException("Password expired for: " + login.getLoginEmail());
} else if (user.getStatus() == UserStatusEnum.ENABLED && user.getType() == UserTypeEnum.NOMINATIVE) {
Map> attributes = new HashMap<>();
- attributes.put(Constants.FLOW_LOGIN_EMAIL, List.of(loginEmail));
- attributes.put(Constants.FLOW_LOGIN_CUSTOMER_ID, List.of(loginCustomerId));
+ attributes.put(FLOW_LOGIN_EMAIL, List.of(login.getLoginEmail()));
+ attributes.put(FLOW_LOGIN_CUSTOMER_ID, List.of(login.getLoginCustomerId()));
- if (surrogateEmail != null) {
- attributes.put(Constants.FLOW_SURROGATE_EMAIL, List.of(surrogateEmail));
- attributes.put(Constants.FLOW_SURROGATE_CUSTOMER_ID, List.of(surrogateCustomerId));
+ if (login.getSurrogateEmail() != null) {
+ attributes.put(FLOW_SURROGATE_EMAIL, List.of(login.getSurrogateEmail()));
+ attributes.put(FLOW_SURROGATE_CUSTOMER_ID, List.of(login.getSurrogateCustomerId()));
}
- final Principal principal = principalFactory.createPrincipal(loginEmail, attributes);
+ Principal principal;
+ try {
+ principal = principalFactory.createPrincipal(login.getLoginEmail(), attributes);
+ } catch (final Throwable e) {
+ LOGGER.error("Error creating principal", e);
+ throw new PreventedException(e);
+ }
LOGGER.debug("Successful authentication, created principal: {}", principal);
return createHandlerResult(transformedCredential, principal, new ArrayList<>());
} else {
- LOGGER.debug("Cannot login user: {} ({})", loginEmail, loginCustomerId);
- throw new AccountException("Disabled or cannot login user: " + loginEmail);
+ LOGGER.debug("Cannot login user: {} ({})", login.getLoginEmail(), login.getLoginCustomerId());
+ throw new AccountException("Disabled or cannot login user: " + login.getLoginEmail());
}
} else {
- LOGGER.debug("No user found for: {} ({})", loginEmail, loginCustomerId);
- throw new AccountNotFoundException("Bad credentials for: " + loginEmail);
+ LOGGER.debug("No user found for: {} ({})", login.getLoginEmail(), login.getLoginCustomerId());
+ throw new AccountNotFoundException("Bad credentials for: " + login.getLoginEmail());
}
} catch (final InvalidAuthenticationException e) {
- LOGGER.error("Bad credentials for username: {} ({})", loginEmail, loginCustomerId);
- throw new CredentialNotFoundException("Bad credentials for username: " + loginEmail);
+ LOGGER.error("Bad credentials for username: {} ({})", login.getLoginEmail(), login.getLoginCustomerId());
+ throw new CredentialNotFoundException("Bad credentials for username: " + login.getLoginEmail());
} catch (final TooManyRequestsException e) {
- LOGGER.error("Too many login attempts for username: {} ({})", loginEmail, loginCustomerId);
- throw new AccountLockedException("Too many login attempts for username: " + loginEmail);
+ LOGGER.error(
+ "Too many login attempts for username: {} ({})",
+ login.getLoginEmail(),
+ login.getLoginCustomerId()
+ );
+ throw new AccountLockedException("Too many login attempts for username: " + login.getLoginEmail());
} catch (final InvalidFormatException e) {
- LOGGER.error("Bad status for username: {} ({})", loginEmail, loginCustomerId);
- throw new AccountDisabledException("Bad status: " + loginEmail);
+ LOGGER.error("Bad status for username: {} ({})", login.getLoginEmail(), login.getLoginCustomerId());
+ throw new AccountDisabledException("Bad status: " + login.getLoginEmail());
} catch (final VitamUIException e) {
- LOGGER.error(String.format("Unexpected exception for username: %s(%s)", loginEmail, loginCustomerId), e);
+ LOGGER.error(
+ "Unexpected exception for username: {}({})",
+ login.getLoginEmail(),
+ login.getLoginCustomerId(),
+ e
+ );
throw new PreventedException(e);
}
}
protected boolean mustChangePassword(final UserDto user) {
- val pwdExpirationDate = user.getPasswordExpirationDate();
+ var pwdExpirationDate = user.getPasswordExpirationDate();
return (pwdExpirationDate == null || pwdExpirationDate.isBefore(OffsetDateTime.now()));
}
+
+ private LoginRequestDto buildLoginRequestFromFlowScopeData(String originalPassword) {
+ var requestContext = RequestContextHolder.getRequestContext();
+ var flowScope = requestContext.getFlowScope();
+
+ String loginEmail = flowScope.getRequiredString(FLOW_LOGIN_EMAIL);
+ String loginCustomerId = flowScope.getRequiredString(FLOW_LOGIN_CUSTOMER_ID);
+ String surrogateEmail = flowScope.getString(FLOW_SURROGATE_EMAIL);
+ String surrogateCustomerId = flowScope.getString(FLOW_SURROGATE_CUSTOMER_ID);
+ String ip = extractClientIp(requestContext);
+
+ logAuthenticationAttempt(loginEmail, loginCustomerId, surrogateEmail, surrogateCustomerId, ip);
+
+ return buildLoginRequest(
+ originalPassword,
+ loginEmail,
+ loginCustomerId,
+ surrogateEmail,
+ surrogateCustomerId,
+ ip
+ );
+ }
+
+ private String extractClientIp(RequestContext requestContext) {
+ var externalContext = requestContext.getExternalContext();
+ var request = (HttpServletRequest) externalContext.getNativeRequest();
+ return request.getHeader(ipHeaderName);
+ }
+
+ private void logAuthenticationAttempt(
+ String loginEmail,
+ String loginCustomerId,
+ String surrogateEmail,
+ String surrogateCustomerId,
+ String ip
+ ) {
+ LOGGER.debug(
+ "Authenticating loginEmail={} loginCustomerId={} surrogateEmail={} surrogateCustomerId={} ip={}",
+ loginEmail,
+ loginCustomerId,
+ surrogateEmail,
+ surrogateCustomerId,
+ ip
+ );
+ }
+
+ private LoginRequestDto buildLoginRequest(
+ String password,
+ String loginEmail,
+ String loginCustomerId,
+ String surrogateEmail,
+ String surrogateCustomerId,
+ String ip
+ ) {
+ var login = new LoginRequestDto();
+ login.setLoginEmail(loginEmail);
+ login.setLoginCustomerId(loginCustomerId);
+ login.setPassword(password);
+ login.setSurrogateEmail(surrogateEmail);
+ login.setSurrogateCustomerId(surrogateCustomerId);
+ login.setIp(ip);
+ return login;
+ }
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java
index ab2a2d8138c..209b4fa2044 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/authentication/UserPrincipalResolver.java
@@ -36,21 +36,18 @@
*/
package fr.gouv.vitamui.cas.authentication;
-import fr.gouv.vitamui.cas.provider.ProvidersService;
+import fr.gouv.vitamui.cas.delegation.ProvidersService;
import fr.gouv.vitamui.cas.util.Constants;
-import fr.gouv.vitamui.cas.util.Utils;
import fr.gouv.vitamui.cas.x509.CertificateParser;
import fr.gouv.vitamui.cas.x509.X509AttributeMapping;
-import fr.gouv.vitamui.commons.api.domain.ProfileDto;
-import fr.gouv.vitamui.commons.api.domain.UserDto;
import fr.gouv.vitamui.commons.api.enums.UserStatusEnum;
import fr.gouv.vitamui.commons.api.utils.CasJsonWrapper;
-import fr.gouv.vitamui.commons.security.client.dto.AuthUserDto;
-import fr.gouv.vitamui.iam.client.CasRestClient;
import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto;
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
+import fr.gouv.vitamui.iam.openapiclient.CasApi;
+import fr.gouv.vitamui.iam.openapiclient.domain.AuthUserDto;
import lombok.RequiredArgsConstructor;
-import lombok.val;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apereo.cas.adaptors.x509.authentication.principal.X509CertificateCredential;
import org.apereo.cas.authentication.AuthenticationHandler;
@@ -68,8 +65,6 @@
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.jee.context.JEEContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.webflow.execution.RequestContextHolder;
@@ -80,11 +75,11 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import static fr.gouv.vitamui.commons.api.CommonConstants.ADDRESS_ATTRIBUTE;
import static fr.gouv.vitamui.commons.api.CommonConstants.ANALYTICS_ATTRIBUTE;
@@ -127,6 +122,7 @@
/**
* Resolver to retrieve the user.
*/
+@Slf4j
@RequiredArgsConstructor
public class UserPrincipalResolver implements PrincipalResolver {
@@ -136,14 +132,13 @@ public class UserPrincipalResolver implements PrincipalResolver {
public static final String SUPER_USER_ID_ATTRIBUTE = "superUserId";
public static final String COMPUTED_OTP = "computedOtp";
- private static final Logger LOGGER = LoggerFactory.getLogger(UserPrincipalResolver.class);
public static final String PROVIDER_PROTOCOL_TYPE_CERTIFICAT = "CERTIFICAT";
- private final PrincipalFactory principalFactory;
+ private static final String DEFAULT_PROVIDER = "";
- private final CasRestClient casRestClient;
+ private final PrincipalFactory principalFactory;
- private final Utils utils;
+ private final CasApi casApi;
private final SessionStore sessionStore;
@@ -161,16 +156,17 @@ public class UserPrincipalResolver implements PrincipalResolver {
public Principal resolve(
final Credential credential,
final Optional optPrincipal,
- final Optional handler
+ final Optional handler,
+ final Optional service
) {
// OAuth 2 authorization code flow (client credentials authentication)
if (optPrincipal.isEmpty()) {
return NullPrincipal.getInstance();
}
- val principal = optPrincipal.get();
- val principalId = principal.getId();
- val requestContext = RequestContextHolder.getRequestContext();
+ final var principal = optPrincipal.get();
+ final var principalId = principal.getId();
+ final var requestContext = RequestContextHolder.getRequestContext();
final boolean subrogationCall;
String loginEmail;
@@ -184,7 +180,7 @@ public Principal resolve(
if (credential instanceof X509CertificateCredential) {
String emailFromCertificate;
try {
- val certificate = ((X509CertificateCredential) credential).getCertificate();
+ final var certificate = ((X509CertificateCredential) credential).getCertificate();
emailFromCertificate = CertificateParser.extract(certificate, x509EmailAttributeMapping);
technicalUserId = Optional.ofNullable(
CertificateParser.extract(certificate, x509IdentifierAttributeMapping)
@@ -199,7 +195,8 @@ public Principal resolve(
String userDomain;
- // If the certificate does not contain the user mail, then we use the default domain configured
+ // If the certificate does not contain the user mail, then we use the default
+ // domain configured
if (
StringUtils.isBlank(emailFromCertificate) || !EMAIL_VALID_REGEXP.matcher(emailFromCertificate).matches()
) {
@@ -210,16 +207,17 @@ public Principal resolve(
userDomain = emailFromCertificate;
}
- // Certificate authn mode does not support multi-domain. Ensure a single provider matches user email.
- val availableProvidersForUserDomain = identityProviderHelper.findAllProvidersByUserIdentifier(
+ // Certificate authn mode does not support multi-domain. Ensure a single
+ // provider matches user email.
+ final var availableProvidersForUserDomain = identityProviderHelper.findAllProvidersByUserIdentifier(
providersService.getProviders(),
userDomain
);
- var certProviders = availableProvidersForUserDomain
+ final var certProviders = availableProvidersForUserDomain
.stream()
.filter(p -> p.getProtocoleType().equals(PROVIDER_PROTOCOL_TYPE_CERTIFICAT))
- .collect(Collectors.toList());
+ .toList();
if (certProviders.isEmpty()) {
LOGGER.warn(
@@ -236,7 +234,7 @@ public Principal resolve(
return NullPrincipal.getInstance();
}
- IdentityProviderDto providerDto = certProviders.get(0);
+ IdentityProviderDto providerDto = certProviders.getFirst();
userProviderId = providerDto.getId();
loginCustomerId = providerDto.getCustomerId();
} else if (credential instanceof SurrogateUsernamePasswordCredential) {
@@ -244,35 +242,35 @@ public Principal resolve(
technicalUserId = Optional.empty();
subrogationCall = true;
- loginEmail = (String) principal.getAttributes().get(Constants.FLOW_SURROGATE_EMAIL).get(0);
- loginCustomerId = (String) principal.getAttributes().get(Constants.FLOW_SURROGATE_CUSTOMER_ID).get(0);
- superUserEmail = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_EMAIL).get(0);
- superUserCustomerId = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_CUSTOMER_ID).get(0);
+ loginEmail = (String) principal.getAttributes().get(Constants.FLOW_SURROGATE_EMAIL).getFirst();
+ loginCustomerId = (String) principal.getAttributes().get(Constants.FLOW_SURROGATE_CUSTOMER_ID).getFirst();
+ superUserEmail = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_EMAIL).getFirst();
+ superUserCustomerId = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_CUSTOMER_ID).getFirst();
} else if (credential instanceof UsernamePasswordCredential) {
// login/password
userProviderId = null;
technicalUserId = Optional.empty();
subrogationCall = false;
- loginEmail = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_EMAIL).get(0);
- loginCustomerId = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_CUSTOMER_ID).get(0);
+ loginEmail = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_EMAIL).getFirst();
+ loginCustomerId = (String) principal.getAttributes().get(Constants.FLOW_LOGIN_CUSTOMER_ID).getFirst();
superUserEmail = null;
superUserCustomerId = null;
} else {
// authentication delegation (+ surrogation)
- val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
- val response = WebUtils.getHttpServletResponseFromExternalWebflowContext(requestContext);
- val webContext = new JEEContext(request, response);
- val clientCredential = (ClientCredential) credential;
- val providerName = clientCredential.getClientName();
- val provider = identityProviderHelper
+ final var request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
+ final var response = WebUtils.getHttpServletResponseFromExternalWebflowContext(requestContext);
+ final var webContext = new JEEContext(request, response);
+ final var clientCredential = (ClientCredential) credential;
+ final var providerName = clientCredential.getClientName();
+ final var provider = identityProviderHelper
.findByTechnicalName(providersService.getProviders(), providerName)
.get();
- val mailAttribute = provider.getMailAttribute();
+ final var mailAttribute = provider.getMailAttribute();
String email = principalId;
if (CommonHelper.isNotBlank(mailAttribute)) {
- val mails = principal.getAttributes().get(mailAttribute);
- if (CollectionUtils.isEmpty(mails) || CommonHelper.isBlank((String) mails.get(0))) {
+ final var mails = principal.getAttributes().get(mailAttribute);
+ if (CollectionUtils.isEmpty(mails) || CommonHelper.isBlank((String) mails.getFirst())) {
LOGGER.error(
"Provider: '{}' requested specific mail attribute: '{}' for id, but attribute does not exist or has no value",
providerName,
@@ -280,7 +278,7 @@ public Principal resolve(
);
return NullPrincipal.getInstance();
} else {
- val mail = (String) mails.get(0);
+ final var mail = (String) mails.getFirst();
LOGGER.info(
"Provider: '{}' requested specific mail attribute: '{}' for id: '{}' replaced by: '{}'",
providerName,
@@ -292,11 +290,11 @@ public Principal resolve(
}
}
- val identifierAttribute = provider.getIdentifierAttribute();
+ final var identifierAttribute = provider.getIdentifierAttribute();
String identifier = principalId;
if (CommonHelper.isNotBlank(identifierAttribute)) {
- val identifiers = principal.getAttributes().get(identifierAttribute);
- if (CollectionUtils.isEmpty(identifiers) || CommonHelper.isBlank((String) identifiers.get(0))) {
+ final var identifiers = principal.getAttributes().get(identifierAttribute);
+ if (CollectionUtils.isEmpty(identifiers) || CommonHelper.isBlank((String) identifiers.getFirst())) {
LOGGER.error(
"Provider: '{}' requested specific identifier attribute: '{}' for id, but attribute does not exist or has no value",
providerName,
@@ -304,7 +302,7 @@ public Principal resolve(
);
return NullPrincipal.getInstance();
} else {
- val identifierAttr = (String) identifiers.get(0);
+ final var identifierAttr = (String) identifiers.getFirst();
LOGGER.info(
"Provider: '{}' requested specific identifier attribute: '{}' for id: '{}' replaced by: '{}'",
providerName,
@@ -378,20 +376,25 @@ public Principal resolve(
}
LOGGER.debug("Computed embedded: {}", embedded);
- final UserDto user = casRestClient.getUser(
- utils.buildContext(loginEmail),
+ // FIXME: The new vitam client not allow null providerId but handles correctly empty strings...
+ // TODO: Need to investigate and fix this behavior
+ if (userProviderId == null) {
+ userProviderId = DEFAULT_PROVIDER;
+ }
+
+ final AuthUserDto user = casApi.getUser(
loginEmail,
loginCustomerId,
userProviderId,
- technicalUserId,
- Optional.of(embedded)
+ technicalUserId.orElse(null),
+ embedded
);
if (user == null) {
LOGGER.debug("No user resolved for: {}", loginEmail);
return null;
} else if (user.getStatus() != UserStatusEnum.ENABLED) {
- LOGGER.debug("User cannot login: {} - User {}", loginEmail, user.toString());
+ LOGGER.debug("User cannot login: {} - User {}", loginEmail, user);
return null;
}
@@ -399,18 +402,18 @@ public Principal resolve(
loginEmail = user.getEmail();
}
- val attributes = new HashMap>();
+ final var attributes = new HashMap>();
attributes.put(USER_ID_ATTRIBUTE, Collections.singletonList(user.getId()));
attributes.put(CUSTOMER_ID_ATTRIBUTE, Collections.singletonList(user.getCustomerId()));
attributes.put(EMAIL_ATTRIBUTE, Collections.singletonList(loginEmail));
attributes.put(FIRSTNAME_ATTRIBUTE, Collections.singletonList(user.getFirstname()));
attributes.put(LASTNAME_ATTRIBUTE, Collections.singletonList(user.getLastname()));
attributes.put(IDENTIFIER_ATTRIBUTE, Collections.singletonList(user.getIdentifier()));
- val otp = user.isOtp();
+ final var otp = user.isOtp();
attributes.put(OTP_ATTRIBUTE, Collections.singletonList(otp));
- val otpUsername = subrogationCall ? superUserEmail : loginEmail;
- val otpCustomerId = subrogationCall ? superUserCustomerId : loginCustomerId;
- val computedOtp =
+ final var otpUsername = subrogationCall ? superUserEmail : loginEmail;
+ final var otpCustomerId = subrogationCall ? superUserCustomerId : loginCustomerId;
+ var computedOtp =
otp &&
identityProviderHelper.identifierMatchProviderPattern(
providersService.getProviders(),
@@ -433,18 +436,11 @@ public Principal resolve(
attributes.put(ADDRESS_ATTRIBUTE, Collections.singletonList(new CasJsonWrapper(user.getAddress())));
attributes.put(ANALYTICS_ATTRIBUTE, Collections.singletonList(new CasJsonWrapper(user.getAnalytics())));
attributes.put(INTERNAL_CODE, Collections.singletonList(user.getInternalCode()));
- UserDto superUser = null;
+ AuthUserDto superUser = null;
if (subrogationCall) {
attributes.put(SUPER_USER_ATTRIBUTE, Collections.singletonList(superUserEmail));
attributes.put(SUPER_USER_CUSTOMER_ID_ATTRIBUTE, Collections.singletonList(superUserCustomerId));
- superUser = casRestClient.getUser(
- utils.buildContext(superUserEmail),
- superUserEmail,
- superUserCustomerId,
- null,
- Optional.empty(),
- Optional.empty()
- );
+ superUser = casApi.getUser(superUserEmail, superUserCustomerId, userProviderId, null, null);
if (superUser == null) {
LOGGER.debug("No super user found for: {}", superUserEmail);
return NullPrincipal.getInstance();
@@ -452,33 +448,26 @@ public Principal resolve(
attributes.put(SUPER_USER_IDENTIFIER_ATTRIBUTE, Collections.singletonList(superUser.getIdentifier()));
attributes.put(SUPER_USER_ID_ATTRIBUTE, Collections.singletonList(superUser.getId()));
}
- if (user instanceof AuthUserDto) {
- final AuthUserDto authUser = (AuthUserDto) user;
- attributes.put(
- PROFILE_GROUP_ATTRIBUTE,
- Collections.singletonList(new CasJsonWrapper(authUser.getProfileGroup()))
- );
- attributes.put(CUSTOMER_IDENTIFIER_ATTRIBUTE, Collections.singletonList(authUser.getCustomerIdentifier()));
- attributes.put(
- BASIC_CUSTOMER_ATTRIBUTE,
- Collections.singletonList(new CasJsonWrapper(authUser.getBasicCustomer()))
- );
- attributes.put(AUTHTOKEN_ATTRIBUTE, Collections.singletonList(authUser.getAuthToken()));
- attributes.put(PROOF_TENANT_ID_ATTRIBUTE, Collections.singletonList(authUser.getProofTenantIdentifier()));
- attributes.put(
- TENANTS_BY_APP_ATTRIBUTE,
- Collections.singletonList(new CasJsonWrapper(authUser.getTenantsByApp()))
- );
- attributes.put(SITE_CODE, Collections.singletonList(user.getSiteCode()));
- attributes.put(CENTER_CODES, Collections.singletonList(user.getCenterCodes()));
- final Set roles = new HashSet<>();
- final List profiles = authUser.getProfileGroup().getProfiles();
- profiles.forEach(profile -> profile.getRoles().forEach(role -> roles.add(role.getName())));
- attributes.put(ROLES_ATTRIBUTE, new ArrayList<>(roles));
+
+ if (isTrueAuthUserDtoInstance(user)) {
+ addAuthenticatedUserAttributes(user, attributes);
+ }
+
+ Principal createdPrincipal;
+ try {
+ createdPrincipal = principalFactory.createPrincipal(user.getId(), attributes);
+ } catch (final Throwable e) {
+ LOGGER.error("Error creating principal", e);
+ throw new RuntimeException(e);
}
- val createdPrincipal = principalFactory.createPrincipal(user.getId(), attributes);
if (subrogationCall) {
- val createdSuperPrincipal = principalFactory.createPrincipal(superUser.getId());
+ Principal createdSuperPrincipal;
+ try {
+ createdSuperPrincipal = principalFactory.createPrincipal(superUser.getId());
+ } catch (final Throwable e) {
+ LOGGER.error("Error creating super principal", e);
+ throw new RuntimeException(e);
+ }
return new SurrogatePrincipal(createdSuperPrincipal, createdPrincipal);
} else {
return createdPrincipal;
@@ -498,4 +487,36 @@ public boolean supports(final Credential credential) {
public IPersonAttributeDao getAttributeRepository() {
return null;
}
+
+ private boolean isTrueAuthUserDtoInstance(AuthUserDto authUser) {
+ return authUser.getProfileGroup() != null;
+ }
+
+ private void addAuthenticatedUserAttributes(AuthUserDto authUser, Map> attributes) {
+ attributes.put(
+ PROFILE_GROUP_ATTRIBUTE,
+ Collections.singletonList(new CasJsonWrapper(authUser.getProfileGroup()))
+ );
+ attributes.put(CUSTOMER_IDENTIFIER_ATTRIBUTE, Collections.singletonList(authUser.getCustomerIdentifier()));
+ attributes.put(
+ BASIC_CUSTOMER_ATTRIBUTE,
+ Collections.singletonList(new CasJsonWrapper(authUser.getBasicCustomer()))
+ );
+ attributes.put(AUTHTOKEN_ATTRIBUTE, Collections.singletonList(authUser.getAuthToken()));
+ attributes.put(PROOF_TENANT_ID_ATTRIBUTE, Collections.singletonList(authUser.getProofTenantIdentifier()));
+ attributes.put(
+ TENANTS_BY_APP_ATTRIBUTE,
+ Collections.singletonList(new CasJsonWrapper(authUser.getTenantsByApp()))
+ );
+ attributes.put(SITE_CODE, Collections.singletonList(authUser.getSiteCode()));
+ attributes.put(CENTER_CODES, Collections.singletonList(authUser.getCenterCodes()));
+ final Set roles = new HashSet<>();
+ if (authUser.getProfileGroup() != null) {
+ authUser
+ .getProfileGroup()
+ .getProfiles()
+ .forEach(profile -> profile.getRoles().forEach(role -> roles.add(role.getName())));
+ }
+ attributes.put(ROLES_ATTRIBUTE, new ArrayList<>(roles));
+ }
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java
index d44ab228443..c4111ce5c16 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/AppConfig.java
@@ -36,34 +36,50 @@
*/
package fr.gouv.vitamui.cas.config;
-import fr.gouv.vitamui.cas.authentication.IamSurrogateAuthenticationService;
-import fr.gouv.vitamui.cas.authentication.UserAuthenticationHandler;
+import fr.gouv.vitamui.cas.authentication.LoginPwdAuthenticationHandler;
import fr.gouv.vitamui.cas.authentication.UserPrincipalResolver;
-import fr.gouv.vitamui.cas.pm.IamPasswordManagementService;
-import fr.gouv.vitamui.cas.provider.ProvidersService;
+import fr.gouv.vitamui.cas.delegation.CustomDelegatedIdentityProviders;
+import fr.gouv.vitamui.cas.delegation.ProvidersService;
+import fr.gouv.vitamui.cas.password.IamPasswordManagementService;
+import fr.gouv.vitamui.cas.surrogation.IamSurrogateAuthenticationService;
import fr.gouv.vitamui.cas.ticket.CustomOAuth20DefaultAccessTokenFactory;
import fr.gouv.vitamui.cas.ticket.DynamicTicketGrantingTicketFactory;
+import fr.gouv.vitamui.cas.util.IamApiDecorator;
import fr.gouv.vitamui.cas.util.Utils;
import fr.gouv.vitamui.cas.x509.X509AttributeMapping;
import fr.gouv.vitamui.commons.security.client.config.password.PasswordConfiguration;
import fr.gouv.vitamui.commons.security.client.password.PasswordValidator;
-import fr.gouv.vitamui.iam.client.CasRestClient;
-import fr.gouv.vitamui.iam.client.IamRestClientFactory;
-import fr.gouv.vitamui.iam.client.IdentityProviderRestClient;
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
import fr.gouv.vitamui.iam.common.utils.Pac4jClientBuilder;
+import fr.gouv.vitamui.iam.openapiclient.CasApi;
+import fr.gouv.vitamui.iam.openapiclient.CustomersApi;
+import fr.gouv.vitamui.iam.openapiclient.IamApiClientsFactory;
+import fr.gouv.vitamui.iam.openapiclient.IdentityProvidersApi;
+import io.micrometer.observation.ObservationRegistry;
+import jakarta.validation.constraints.NotNull;
import lombok.SneakyThrows;
-import lombok.val;
+import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.audit.AuditableExecution;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
+import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
+import org.apereo.cas.authentication.AuthenticationSystemSupport;
+import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
+import org.apereo.cas.authentication.principal.DelegatedAuthenticationCredentialExtractor;
+import org.apereo.cas.authentication.principal.DelegatedAuthenticationPreProcessor;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.support.Beans;
+import org.apereo.cas.logout.LogoutExecutionPlan;
+import org.apereo.cas.logout.slo.SingleLogoutRequestExecutor;
import org.apereo.cas.mfa.simple.CasSimpleMultifactorTokenCommunicationStrategy;
import org.apereo.cas.mfa.simple.ticket.CasSimpleMultifactorAuthenticationTicket;
+import org.apereo.cas.pac4j.client.DelegatedClientAuthenticationRequestCustomizer;
+import org.apereo.cas.pac4j.client.DelegatedClientIdentityProviderRedirectionStrategy;
+import org.apereo.cas.pac4j.client.DelegatedClientNameExtractor;
+import org.apereo.cas.pac4j.client.DelegatedIdentityProviders;
import org.apereo.cas.pm.PasswordHistoryService;
import org.apereo.cas.pm.PasswordManagementService;
import org.apereo.cas.services.ServicesManager;
@@ -71,156 +87,69 @@
import org.apereo.cas.ticket.ExpirationPolicyBuilder;
import org.apereo.cas.ticket.TicketCatalog;
import org.apereo.cas.ticket.TicketDefinition;
+import org.apereo.cas.ticket.TicketFactory;
import org.apereo.cas.ticket.TicketGrantingTicketFactory;
import org.apereo.cas.ticket.UniqueTicketIdGenerator;
import org.apereo.cas.ticket.accesstoken.OAuth20AccessToken;
import org.apereo.cas.ticket.accesstoken.OAuth20AccessTokenFactory;
import org.apereo.cas.ticket.accesstoken.OAuth20DefaultAccessToken;
import org.apereo.cas.ticket.registry.TicketRegistry;
+import org.apereo.cas.ticket.tracking.TicketTrackingPolicy;
import org.apereo.cas.token.JwtBuilder;
import org.apereo.cas.util.crypto.CipherExecutor;
+import org.apereo.cas.util.spring.beans.BeanSupplier;
+import org.apereo.cas.web.cookie.CasCookieBuilder;
+import org.apereo.cas.web.flow.DelegatedClientAuthenticationConfigurationContext;
+import org.apereo.cas.web.flow.DelegatedClientIdentityProviderAuthorizer;
+import org.apereo.cas.web.flow.DelegatedClientIdentityProviderConfigurationPostProcessor;
+import org.apereo.cas.web.flow.DelegatedClientIdentityProviderConfigurationProducer;
+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.apereo.cas.web.support.ArgumentExtractor;
import org.pac4j.core.client.Clients;
import org.pac4j.core.context.session.SessionStore;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.cloud.context.config.annotation.RefreshScope;
-import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.Ordered;
+import org.springframework.data.mongodb.observability.ContextProviderFactory;
+import org.springframework.data.mongodb.observability.MongoObservationCommandListener;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mail.javamail.JavaMailSender;
-import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static fr.gouv.vitamui.commons.api.CommonConstants.X_ORIGIN_HEADER_EXTERNAL;
+import static fr.gouv.vitamui.commons.api.CommonConstants.X_ORIGIN_HEADER_NAME;
/**
* Configure all beans to customize the CAS server.
*/
+@Slf4j
@Configuration
@EnableConfigurationProperties(
{ CasConfigurationProperties.class, IamClientConfigurationProperties.class, PasswordConfiguration.class }
)
public class AppConfig extends BaseTicketCatalogConfigurer {
- private static final Logger LOGGER = LoggerFactory.getLogger(AppConfig.class);
-
- @Autowired
- private CasConfigurationProperties casProperties;
-
- @Autowired
- @Qualifier("servicesManager")
- private ServicesManager servicesManager;
-
- @Autowired
- @Qualifier("principalFactory")
- private PrincipalFactory principalFactory;
-
- @Autowired
- private ApplicationEventPublisher eventPublisher;
-
- @Autowired
- @Qualifier("surrogateAuthenticationService")
- private SurrogateAuthenticationService surrogateAuthenticationService;
-
- @Autowired
- private IamClientConfigurationProperties iamClientProperties;
-
- @Autowired
- @Qualifier("registeredServiceAccessStrategyEnforcer")
- private AuditableExecution registeredServiceAccessStrategyEnforcer;
-
- @Autowired
- @Qualifier("surrogateEligibilityAuditableExecution")
- private AuditableExecution surrogateEligibilityAuditableExecution;
-
- @Autowired
- @Qualifier("ticketGrantingTicketUniqueIdGenerator")
- private UniqueTicketIdGenerator ticketGrantingTicketUniqueIdGenerator;
-
- @Autowired
- @Qualifier("accessTokenJwtBuilder")
- private JwtBuilder accessTokenJwtBuilder;
-
- @Autowired
- @Qualifier("grantingTicketExpirationPolicy")
- private ObjectProvider grantingTicketExpirationPolicy;
-
- @Autowired
- private CipherExecutor protocolTicketCipherExecutor;
-
- @Autowired
- @Qualifier("accessTokenExpirationPolicy")
- private ExpirationPolicyBuilder accessTokenExpirationPolicy;
-
- @Autowired
- private JavaMailSender mailSender;
-
- @Autowired
- @Qualifier("centralAuthenticationService")
- private ObjectProvider centralAuthenticationService;
-
- @Autowired
- @Qualifier("passwordManagementCipherExecutor")
- private CipherExecutor passwordManagementCipherExecutor;
-
- @Autowired
- @Qualifier("passwordHistoryService")
- private PasswordHistoryService passwordHistoryService;
-
- @Autowired
- private PasswordConfiguration passwordConfiguration;
-
- @Value("${cas.secret.token}")
- @NotNull
- private String tokenApiCas;
-
- @Value("${ip.header}")
- private String ipHeaderName;
-
- @Value("${vitamui.cas.tenant.identifier}")
- private Integer casTenantIdentifier;
-
- @Value("${vitamui.cas.identity}")
- private String casIdentity;
-
- @Value("${theme.vitamui-logo-large:#{null}}")
- private String vitamuiLogoLargePath;
-
- @Value("${theme.vitamui-favicon:#{null}}")
- private String vitamuiFaviconPath;
-
- @Value("${vitamui.authn.x509.emailAttribute:}")
- private String x509EmailAttribute;
-
- @Value("${vitamui.authn.x509.emailAttributeParsing:}")
- private String x509EmailAttributeParsing;
-
- @Value("${vitamui.authn.x509.emailAttributeExpansion:}")
- private String x509EmailAttributeExpansion;
-
- @Value("${vitamui.authn.x509.identifierAttribute:}")
- private String x509IdentifierAttribute;
-
- @Value("${vitamui.authn.x509.identifierAttributeParsing:}")
- private String x509IdentifierAttributeParsing;
-
- @Value("${vitamui.authn.x509.identifierAttributeExpansion:}")
- private String x509IdentifierAttributeExpansion;
-
- @Value("${vitamui.authn.x509.defaultDomain:}")
- private String x509DefaultDomain;
-
// overrides the CAS specific message converter to prevent
- // the CasRestExternalClient to use the 'application/vnd.cas.services+yaml;charset=UTF-8'
+ // the CasRestExternalClient to use the
+ // 'application/vnd.cas.services+yaml;charset=UTF-8'
// content type and to fail
@Bean
public HttpMessageConverter yamlHttpMessageConverter() {
@@ -233,34 +162,43 @@ public PasswordValidator passwordValidator() {
}
@Bean
- public UserAuthenticationHandler userAuthenticationHandler(
- final IamRestClientFactory iamRestClientFactory,
- final CasRestClient casRestClient
+ public LoginPwdAuthenticationHandler loginPwdAuthenticationHandler(
+ final CasApi casApi,
+ @Value("${ip.header}") final String ipHeaderName,
+ @Qualifier("principalFactory") final PrincipalFactory principalFactory,
+ @Qualifier("servicesManager") final ServicesManager servicesManager
) {
- return new UserAuthenticationHandler(servicesManager, principalFactory, casRestClient, utils(), ipHeaderName);
+ return new LoginPwdAuthenticationHandler(servicesManager, principalFactory, casApi, ipHeaderName);
}
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public PrincipalResolver defaultPrincipalResolver(
- final ProvidersService providersService,
+ @Value("${vitamui.authn.x509.emailAttribute:}") final String x509EmailAttribute,
+ @Value("${vitamui.authn.x509.emailAttributeParsing:}") final String x509EmailAttributeParsing,
+ @Value("${vitamui.authn.x509.emailAttributeExpansion:}") final String x509EmailAttributeExpansion,
+ @Value("${vitamui.authn.x509.identifierAttribute:}") final String x509IdentifierAttribute,
+ @Value("${vitamui.authn.x509.identifierAttributeParsing:}") final String x509IdentifierAttributeParsing,
+ @Value("${vitamui.authn.x509.identifierAttributeExpansion:}") final String x509IdentifierAttributeExpansion,
+ @Value("${vitamui.authn.x509.defaultDomain:}") final String x509DefaultDomain,
@Qualifier("delegatedClientDistributedSessionStore") final SessionStore delegatedClientDistributedSessionStore,
- final CasRestClient casRestClient
+ @Qualifier(PrincipalFactory.BEAN_NAME) PrincipalFactory principalFactory,
+ final ProvidersService providersService,
+ final CasApi casApi
) {
- val emailMapping = new X509AttributeMapping(
+ final var emailMapping = new X509AttributeMapping(
x509EmailAttribute,
x509EmailAttributeParsing,
x509EmailAttributeExpansion
);
- val identifierMapping = new X509AttributeMapping(
+ final var identifierMapping = new X509AttributeMapping(
x509IdentifierAttribute,
x509IdentifierAttributeParsing,
x509IdentifierAttributeExpansion
);
return new UserPrincipalResolver(
principalFactory,
- casRestClient,
- utils(),
+ casApi,
delegatedClientDistributedSessionStore,
identityProviderHelper(),
providersService,
@@ -272,12 +210,12 @@ public PrincipalResolver defaultPrincipalResolver(
@Bean
public AuthenticationEventExecutionPlanConfigurer registerInternalHandler(
- final UserAuthenticationHandler userAuthenticationHandler,
- @Qualifier("defaultPrincipalResolver") PrincipalResolver defaultPrincipalResolver
+ final LoginPwdAuthenticationHandler loginPwdAuthenticationHandler,
+ @Qualifier("defaultPrincipalResolver") final PrincipalResolver defaultPrincipalResolver
) {
return plan ->
plan.registerAuthenticationHandlerWithPrincipalResolver(
- userAuthenticationHandler,
+ loginPwdAuthenticationHandler,
defaultPrincipalResolver
);
}
@@ -285,7 +223,7 @@ public AuthenticationEventExecutionPlanConfigurer registerInternalHandler(
@Bean
@RefreshScope
public PrincipalResolver surrogatePrincipalResolver(
- @Qualifier("defaultPrincipalResolver") PrincipalResolver defaultPrincipalResolver
+ @Qualifier("defaultPrincipalResolver") final PrincipalResolver defaultPrincipalResolver
) {
return defaultPrincipalResolver;
}
@@ -293,39 +231,83 @@ public PrincipalResolver surrogatePrincipalResolver(
@Bean
@RefreshScope
public PrincipalResolver x509SubjectDNPrincipalResolver(
- @Qualifier("defaultPrincipalResolver") PrincipalResolver defaultPrincipalResolver
+ @Qualifier("defaultPrincipalResolver") final PrincipalResolver defaultPrincipalResolver
) {
return defaultPrincipalResolver;
}
+ /**
+ * We must define our customizer to replace X_ORIGIN header from IamApiClient.java for CAS usage.
+ *
+ * @return a rest template customizer.
+ */
+ @Bean
+ @Qualifier("restTemplateCustomizer")
+ public RestTemplateCustomizer restTemplateCustomizer() {
+ return restTemplate ->
+ restTemplate
+ .getInterceptors()
+ .add((request, body, execution) -> {
+ // Hack for CAS - CAS is considered as an external server requiring proper roles
+ request.getHeaders().set(X_ORIGIN_HEADER_NAME, X_ORIGIN_HEADER_EXTERNAL);
+
+ LOGGER.debug("Final request URI: {}, headers: {}", request.getURI(), request.getHeaders());
+
+ return execution.execute(request, body);
+ });
+ }
+
@Bean
- public IamRestClientFactory iamRestClientFactory(final RestTemplateBuilder restTemplateBuilder) {
- LOGGER.debug("Iam client factory: {}", iamClientProperties);
- return new IamRestClientFactory(iamClientProperties, restTemplateBuilder);
+ public IamApiClientsFactory iamApiClientsFactory(
+ final IamClientConfigurationProperties iamClientProperties,
+ final RestTemplateBuilder restTemplateBuilder,
+ @Qualifier("restTemplateCustomizer") final RestTemplateCustomizer restTemplateCustomizer
+ ) {
+ return new IamApiClientsFactory(
+ iamClientProperties,
+ restTemplateBuilder.additionalCustomizers(restTemplateCustomizer)
+ );
}
@Bean
- public CasRestClient casRestClient(final IamRestClientFactory iamRestClientFactory) {
- return iamRestClientFactory.getCasExternalRestClient();
+ public IamApiDecorator iamApiDecorator(Utils utils) {
+ return new IamApiDecorator(utils);
}
@Bean
- public IdentityProviderRestClient identityProviderCrudRestClient(final IamRestClientFactory iamRestClientFactory) {
- return iamRestClientFactory.getIdentityProviderExternalRestClient();
+ public CasApi casApi(final IamApiClientsFactory iamApiClientsFactory, final IamApiDecorator iamApiDecorator) {
+ return iamApiDecorator.decorate(iamApiClientsFactory.getCasApi());
}
- @RefreshScope
@Bean
- public Clients builtClients() {
+ public CustomersApi customersApi(
+ final IamApiClientsFactory iamApiClientsFactory,
+ final IamApiDecorator iamApiDecorator
+ ) {
+ return iamApiDecorator.decorate(iamApiClientsFactory.getCustomersApi());
+ }
+
+ @Bean
+ public IdentityProvidersApi identityProvidersApi(
+ final IamApiClientsFactory iamApiClientsFactory,
+ final IamApiDecorator iamApiDecorator
+ ) {
+ return iamApiDecorator.decorate(iamApiClientsFactory.getIdentityProvidersApi());
+ }
+
+ @Bean
+ @RefreshScope
+ public Clients builtClients(final CasConfigurationProperties casProperties) {
return new Clients(casProperties.getServer().getLoginUrl());
}
@Bean
public ProvidersService providersService(
- @Qualifier("builtClients") final Clients builtClients,
- final IdentityProviderRestClient identityProviderCrudRestClient
+ final Clients builtClients,
+ final IdentityProvidersApi identityProvidersApi,
+ final Pac4jClientBuilder pac4jClientBuilder
) {
- return new ProvidersService(builtClients, identityProviderCrudRestClient, pac4jClientBuilder(), utils());
+ return new ProvidersService(builtClients, identityProvidersApi, pac4jClientBuilder);
}
@Bean
@@ -339,7 +321,13 @@ public IdentityProviderHelper identityProviderHelper() {
}
@Bean
- public Utils utils() {
+ public Utils utils(
+ @Value("${cas_secret_token}") @NotNull final String tokenApiCas,
+ @Value("${vitamui.cas.tenant.identifier}") final Integer casTenantIdentifier,
+ @Value("${vitamui.cas.identity}") final String casIdentity,
+ final JavaMailSender mailSender,
+ final CasConfigurationProperties casProperties
+ ) {
return new Utils(
tokenApiCas,
casTenantIdentifier,
@@ -350,23 +338,43 @@ public Utils utils() {
}
@Bean
- public TicketGrantingTicketFactory defaultTicketGrantingTicketFactory() {
+ public TicketGrantingTicketFactory defaultTicketGrantingTicketFactory(
+ @Qualifier(ServicesManager.BEAN_NAME) ServicesManager servicesManager,
+ @Qualifier(
+ "ticketGrantingTicketUniqueIdGenerator"
+ ) final UniqueTicketIdGenerator ticketGrantingTicketUniqueIdGenerator,
+ @Qualifier("grantingTicketExpirationPolicy") final ObjectProvider<
+ ExpirationPolicyBuilder
+ > grantingTicketExpirationPolicy,
+ @Qualifier("protocolTicketCipherExecutor") final CipherExecutor protocolTicketCipherExecutor,
+ final Utils utils
+ ) {
return new DynamicTicketGrantingTicketFactory(
ticketGrantingTicketUniqueIdGenerator,
grantingTicketExpirationPolicy.getObject(),
protocolTicketCipherExecutor,
servicesManager,
- utils()
+ utils
);
}
@Bean
- @RefreshScope
- public OAuth20AccessTokenFactory defaultAccessTokenFactory() {
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ public OAuth20AccessTokenFactory defaultAccessTokenFactory(
+ @Qualifier("accessTokenIdGenerator") final UniqueTicketIdGenerator accessTokenIdGenerator,
+ @Qualifier("accessTokenExpirationPolicy") final ExpirationPolicyBuilder accessTokenExpirationPolicy,
+ @Qualifier(ServicesManager.BEAN_NAME) final ServicesManager servicesManager,
+ @Qualifier("accessTokenJwtBuilder") final JwtBuilder accessTokenJwtBuilder,
+ @Qualifier(
+ TicketTrackingPolicy.BEAN_NAME_DESCENDANT_TICKET_TRACKING
+ ) final TicketTrackingPolicy descendantTicketsTrackingPolicy
+ ) {
return new CustomOAuth20DefaultAccessTokenFactory(
+ accessTokenIdGenerator,
accessTokenExpirationPolicy,
accessTokenJwtBuilder,
- servicesManager
+ servicesManager,
+ descendantTicketsTrackingPolicy
);
}
@@ -380,40 +388,51 @@ public void configureTicketCatalog(final TicketCatalog plan, final CasConfigurat
Ordered.HIGHEST_PRECEDENCE
);
metadata.getProperties().setStorageName(casProperties.getAuthn().getOauth().getAccessToken().getStorageName());
- val timeout = Beans.newDuration(
+ final var timeout = Beans.newDuration(
casProperties.getAuthn().getOauth().getAccessToken().getMaxTimeToLiveInSeconds()
).getSeconds();
metadata.getProperties().setStorageTimeout(timeout);
- metadata.getProperties().setExcludeFromCascade(casProperties.getLogout().isRemoveDescendantTickets());
+ metadata.getProperties().setExcludeFromCascade(casProperties.getTicket().isTrackDescendantTickets());
registerTicketDefinition(plan, metadata);
}
@RefreshScope
@Bean
@SneakyThrows
- public SurrogateAuthenticationService surrogateAuthenticationService(final CasRestClient casRestClient) {
- return new IamSurrogateAuthenticationService(casRestClient, servicesManager, utils());
+ public SurrogateAuthenticationService surrogateAuthenticationService(
+ final CasApi casApi,
+ @Qualifier(ServicesManager.BEAN_NAME) final ServicesManager servicesManager
+ ) {
+ return new IamSurrogateAuthenticationService(casApi, servicesManager);
}
- @RefreshScope
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@Bean
public PasswordManagementService passwordChangeService(
+ final CasConfigurationProperties casProperties,
+ @Qualifier("passwordManagementCipherExecutor") final CipherExecutor passwordManagementCipherExecutor,
+ @Qualifier(PasswordHistoryService.BEAN_NAME) final PasswordHistoryService passwordHistoryService,
final ProvidersService providersService,
final TicketRegistry ticketRegistry,
- final CasRestClient casRestClient
+ final CasApi casApi,
+ final IdentityProviderHelper identityProviderHelper,
+ final Utils utils,
+ final PasswordValidator passwordValidator,
+ @Qualifier("centralAuthenticationService") final CentralAuthenticationService centralAuthenticationService,
+ final PasswordConfiguration passwordConfiguration
) {
return new IamPasswordManagementService(
casProperties.getAuthn().getPm(),
passwordManagementCipherExecutor,
casProperties.getServer().getPrefix(),
passwordHistoryService,
- casRestClient,
+ casApi,
providersService,
- identityProviderHelper(),
- centralAuthenticationService.getObject(),
- utils(),
+ identityProviderHelper,
+ centralAuthenticationService,
+ utils,
ticketRegistry,
- passwordValidator(),
+ passwordValidator,
passwordConfiguration
);
}
@@ -424,7 +443,7 @@ public CasSimpleMultifactorTokenCommunicationStrategy mfaSimpleMultifactorTokenC
return new CasSimpleMultifactorTokenCommunicationStrategy() {
@Override
public EnumSet determineStrategy(
- CasSimpleMultifactorAuthenticationTicket token
+ final CasSimpleMultifactorAuthenticationTicket token
) {
return EnumSet.of(TokenSharingStrategyOptions.SMS);
}
@@ -432,12 +451,145 @@ public EnumSet determineStrategy(
}
@Bean
- public ServletContextInitializer servletContextInitializer() {
- return new InitContextConfiguration(vitamuiLogoLargePath, vitamuiFaviconPath);
+ public ServletContextInitializer servletContextInitializer(
+ @Value("${theme.vitamui-logo-large:#{null}}") final String vitamuiLargeLogoPath,
+ @Value("${theme.vitamui-favicon:#{null}}") final String vitamuiFaviconPath
+ ) {
+ return new InitContextConfiguration(vitamuiLargeLogoPath, vitamuiFaviconPath);
}
@Bean
public ServletContextInitializer servletPasswordContextInitializer() {
return new InitPasswordConstraintsConfiguration();
}
+
+ @Bean
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ public AuthenticationEventExecutionPlanConfigurer passwordManagementAuthenticationExecutionPlanConfigurer() {
+ return plan -> {};
+ }
+
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ @Bean
+ public DelegatedIdentityProviders delegatedIdentityProviders(final ProvidersService providersService) {
+ return new CustomDelegatedIdentityProviders(providersService);
+ }
+
+ @Bean
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ public DelegatedClientAuthenticationConfigurationContext delegatedClientAuthenticationConfigurationContext(
+ @Qualifier(
+ SingleLogoutRequestExecutor.BEAN_NAME
+ ) final SingleLogoutRequestExecutor defaultSingleLogoutRequestExecutor,
+ @Qualifier(
+ AuditableExecution.AUDITABLE_EXECUTION_DELEGATED_AUTHENTICATION_ACCESS
+ ) final AuditableExecution registeredServiceDelegatedAuthenticationPolicyAuditableEnforcer,
+ @Qualifier(
+ "serviceTicketRequestWebflowEventResolver"
+ ) final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
+ @Qualifier(
+ "initialAuthenticationAttemptWebflowEventResolver"
+ ) final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
+ @Qualifier("adaptiveAuthenticationPolicy") final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
+ final CasConfigurationProperties casProperties,
+ @Qualifier(ServicesManager.BEAN_NAME) final ServicesManager servicesManager,
+ @Qualifier(DelegatedIdentityProviders.BEAN_NAME) final DelegatedIdentityProviders identityProviders,
+ @Qualifier(
+ DelegatedClientIdentityProviderConfigurationProducer.BEAN_NAME
+ ) final DelegatedClientIdentityProviderConfigurationProducer delegatedClientIdentityProviderConfigurationProducer,
+ @Qualifier(
+ "delegatedClientIdentityProviderConfigurationPostProcessor"
+ ) final DelegatedClientIdentityProviderConfigurationPostProcessor delegatedClientIdentityProviderConfigurationPostProcessor,
+ @Qualifier(
+ "delegatedClientDistributedSessionCookieGenerator"
+ ) final CasCookieBuilder delegatedClientDistributedSessionCookieGenerator,
+ @Qualifier(
+ CentralAuthenticationService.BEAN_NAME
+ ) final CentralAuthenticationService centralAuthenticationService,
+ @Qualifier(
+ "pac4jDelegatedClientNameExtractor"
+ ) final DelegatedClientNameExtractor pac4jDelegatedClientNameExtractor,
+ @Qualifier(AuthenticationSystemSupport.BEAN_NAME) final AuthenticationSystemSupport authenticationSystemSupport,
+ @Qualifier(ArgumentExtractor.BEAN_NAME) final ArgumentExtractor argumentExtractor,
+ @Qualifier(TicketRegistry.BEAN_NAME) final TicketRegistry ticketRegistry,
+ @Qualifier("delegatedClientDistributedSessionStore") final SessionStore delegatedClientDistributedSessionStore,
+ @Qualifier(TicketFactory.BEAN_NAME) final TicketFactory ticketFactory,
+ @Qualifier(
+ AuditableExecution.AUDITABLE_EXECUTION_REGISTERED_SERVICE_ACCESS
+ ) final AuditableExecution registeredServiceAccessStrategyEnforcer,
+ @Qualifier(
+ "delegatedClientIdentityProviderRedirectionStrategy"
+ ) final DelegatedClientIdentityProviderRedirectionStrategy delegatedClientIdentityProviderRedirectionStrategy,
+ @Qualifier(
+ SingleSignOnParticipationStrategy.BEAN_NAME
+ ) final SingleSignOnParticipationStrategy webflowSingleSignOnParticipationStrategy,
+ @Qualifier(
+ AuthenticationServiceSelectionPlan.BEAN_NAME
+ ) final AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies,
+ @Qualifier(
+ "delegatedAuthenticationCookieGenerator"
+ ) final CasCookieBuilder delegatedAuthenticationCookieGenerator,
+ @Qualifier(
+ "delegatedAuthenticationCredentialExtractor"
+ ) final DelegatedAuthenticationCredentialExtractor delegatedAuthenticationCredentialExtractor,
+ final ConfigurableApplicationContext applicationContext,
+ @Qualifier(LogoutExecutionPlan.BEAN_NAME) final LogoutExecutionPlan logoutExecutionPlan,
+ final ObjectProvider> customizersProvider,
+ final List delegatedClientIdentityProviderAuthorizers
+ ) {
+ final var customizers = Optional.ofNullable(customizersProvider.getIfAvailable())
+ .orElseGet(ArrayList::new)
+ .stream()
+ .filter(BeanSupplier::isNotProxy)
+ .collect(Collectors.toList());
+
+ final var authorizers = delegatedClientIdentityProviderAuthorizers;
+
+ return DelegatedClientAuthenticationConfigurationContext.builder()
+ .credentialExtractor(delegatedAuthenticationCredentialExtractor)
+ .initialAuthenticationAttemptWebflowEventResolver(initialAuthenticationAttemptWebflowEventResolver)
+ .serviceTicketRequestWebflowEventResolver(serviceTicketRequestWebflowEventResolver)
+ .adaptiveAuthenticationPolicy(adaptiveAuthenticationPolicy)
+ .identityProviders(identityProviders)
+ .ticketRegistry(ticketRegistry)
+ .applicationContext(applicationContext)
+ .servicesManager(servicesManager)
+ .delegatedAuthenticationPolicyEnforcer(registeredServiceDelegatedAuthenticationPolicyAuditableEnforcer)
+ .authenticationSystemSupport(authenticationSystemSupport)
+ .casProperties(casProperties)
+ .centralAuthenticationService(centralAuthenticationService)
+ .authenticationRequestServiceSelectionStrategies(authenticationRequestServiceSelectionStrategies)
+ .singleSignOnParticipationStrategy(webflowSingleSignOnParticipationStrategy)
+ .sessionStore(delegatedClientDistributedSessionStore)
+ .argumentExtractor(argumentExtractor)
+ .ticketFactory(ticketFactory)
+ .delegatedClientIdentityProvidersProducer(delegatedClientIdentityProviderConfigurationProducer)
+ .delegatedClientIdentityProviderConfigurationPostProcessor(
+ delegatedClientIdentityProviderConfigurationPostProcessor
+ )
+ .delegatedClientCookieGenerator(delegatedAuthenticationCookieGenerator)
+ .delegatedClientDistributedSessionCookieGenerator(delegatedClientDistributedSessionCookieGenerator)
+ .registeredServiceAccessStrategyEnforcer(registeredServiceAccessStrategyEnforcer)
+ .delegatedClientAuthenticationRequestCustomizers(customizers)
+ .delegatedClientNameExtractor(pac4jDelegatedClientNameExtractor)
+ .delegatedClientIdentityProviderAuthorizers(authorizers)
+ .delegatedClientIdentityProviderRedirectionStrategy(delegatedClientIdentityProviderRedirectionStrategy)
+ .singleLogoutRequestExecutor(defaultSingleLogoutRequestExecutor)
+ .logoutExecutionPlan(logoutExecutionPlan)
+ .build();
+ }
+
+ @Bean
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ public DelegatedAuthenticationPreProcessor surrogateDelegatedAuthenticationPreProcessor() {
+ return (principal, client, credential, service) -> principal;
+ }
+
+ @Bean
+ MongoClientSettingsBuilderCustomizer mongoMetricsSynchronousContextProvider(ObservationRegistry registry) {
+ return clientSettingsBuilder ->
+ clientSettingsBuilder
+ .contextProvider(ContextProviderFactory.create(registry))
+ .addCommandListener(new MongoObservationCommandListener(registry));
+ }
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/CustomSurrogateInitialAuthenticationAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/CustomSurrogateInitialAuthenticationAction.java
index 15c22f41183..ecd22a0e0b7 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/CustomSurrogateInitialAuthenticationAction.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/CustomSurrogateInitialAuthenticationAction.java
@@ -1,14 +1,12 @@
package fr.gouv.vitamui.cas.config;
import fr.gouv.vitamui.cas.util.Constants;
-import lombok.val;
+import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.SurrogateUsernamePasswordCredential;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import org.apereo.cas.web.flow.action.SurrogateInitialAuthenticationAction;
import org.apereo.cas.web.flow.actions.BaseCasWebflowAction;
import org.apereo.cas.web.support.WebUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
@@ -16,13 +14,12 @@
/**
* CUSTO: Full rewrite of {@link SurrogateInitialAuthenticationAction}
*/
+@Slf4j
public class CustomSurrogateInitialAuthenticationAction extends BaseCasWebflowAction {
- private static final Logger LOGGER = LoggerFactory.getLogger(CustomSurrogateInitialAuthenticationAction.class);
-
@Override
- protected Event doExecute(RequestContext context) throws Exception {
- val up = WebUtils.getCredential(context, UsernamePasswordCredential.class);
+ protected Event doExecuteInternal(RequestContext context) {
+ final var up = WebUtils.getCredential(context, UsernamePasswordCredential.class);
if (up == null) {
LOGGER.debug(
"Provided credentials cannot be found, or are already of type [{}]",
@@ -31,7 +28,7 @@ protected Event doExecute(RequestContext context) throws Exception {
return null;
}
- val flowScope = context.getFlowScope();
+ final var flowScope = context.getFlowScope();
if (isSubrogationMode(flowScope)) {
String surrogateEmail = (String) flowScope.get(Constants.FLOW_SURROGATE_EMAIL);
String surrogateCustomerId = (String) flowScope.get(Constants.FLOW_SURROGATE_CUSTOMER_ID);
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitContextConfiguration.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitContextConfiguration.java
index 8acfac0d011..cb2b4ef0e01 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitContextConfiguration.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitContextConfiguration.java
@@ -37,14 +37,13 @@
package fr.gouv.vitamui.cas.config;
import fr.gouv.vitamui.cas.util.Constants;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.xml.bind.DatatypeConverter;
import lombok.RequiredArgsConstructor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.ServletContextInitializer;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -53,17 +52,14 @@
/**
* Custom context initializer to pre-fill logo and favicon.
*/
+@Slf4j
@RequiredArgsConstructor
public class InitContextConfiguration implements ServletContextInitializer {
- private static final Logger LOGGER = LoggerFactory.getLogger(InitContextConfiguration.class);
-
private final String vitamuiLogoLargePath;
private final String vitamuiFaviconPath;
- private static final String VITAMUI_LOGO_LARGE = "vitamuiLogoLarge";
-
@Override
public void onStartup(final ServletContext servletContext) throws ServletException {
if (vitamuiLogoLargePath != null) {
@@ -76,7 +72,7 @@ public void onStartup(final ServletContext servletContext) throws ServletExcepti
// default PNG
base64Logo = "data:image/png;base64," + base64Logo;
}
- servletContext.setAttribute(VITAMUI_LOGO_LARGE, base64Logo);
+ servletContext.setAttribute(Constants.VITAMUI_LOGO_LARGE, base64Logo);
} catch (final IOException e) {
LOGGER.warn("Can't find vitam ui large logo", e);
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitPasswordConstraintsConfiguration.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitPasswordConstraintsConfiguration.java
index 780c0b7da31..207828c8d00 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitPasswordConstraintsConfiguration.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/InitPasswordConstraintsConfiguration.java
@@ -38,22 +38,20 @@
import fr.gouv.vitamui.cas.util.Constants;
import fr.gouv.vitamui.commons.security.client.config.password.PasswordConfiguration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletContextInitializer;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
import java.util.Objects;
/**
* Custom context initializer for password complexity configuration.
*/
+@Slf4j
public class InitPasswordConstraintsConfiguration implements ServletContextInitializer {
- private static final Logger LOGGER = LoggerFactory.getLogger(InitPasswordConstraintsConfiguration.class);
-
@Autowired
private PasswordConfiguration passwordConfiguration;
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java
index 8a7f88fb9ef..6c10724e8d6 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebConfig.java
@@ -36,30 +36,51 @@
*/
package fr.gouv.vitamui.cas.config;
-import fr.gouv.vitamui.cas.provider.ProvidersService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import fr.gouv.vitamui.cas.delegation.ProvidersService;
+import fr.gouv.vitamui.cas.password.CustomCasWebSecurityConfigurerAdapter;
+import fr.gouv.vitamui.cas.password.ResetPasswordController;
+import fr.gouv.vitamui.cas.util.Utils;
import fr.gouv.vitamui.cas.web.CustomCorsProcessor;
import fr.gouv.vitamui.cas.web.CustomOidcCasClientRedirectActionBuilder;
+import fr.gouv.vitamui.cas.web.CustomOidcRevocationEndpointController;
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
import lombok.val;
import org.apereo.cas.configuration.CasConfigurationProperties;
+import org.apereo.cas.notifications.CommunicationsManager;
+import org.apereo.cas.oidc.OidcConfigurationContext;
import org.apereo.cas.oidc.util.OidcRequestSupport;
+import org.apereo.cas.oidc.web.controllers.token.OidcRevocationEndpointController;
+import org.apereo.cas.pm.PasswordManagementService;
+import org.apereo.cas.pm.PasswordResetUrlBuilder;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.services.web.support.RegisteredServiceCorsConfigurationSource;
import org.apereo.cas.support.oauth.web.OAuth20RequestParameterResolver;
import org.apereo.cas.support.oauth.web.response.OAuth20CasClientRedirectActionBuilder;
+import org.apereo.cas.web.CasWebSecurityConfigurer;
import org.apereo.cas.web.support.ArgumentExtractor;
import org.pac4j.cas.client.CasClient;
import org.pac4j.core.client.Client;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
+import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.HierarchicalMessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
+import java.util.List;
+
/**
* Web customizations.
*/
@@ -75,9 +96,12 @@ public OAuth20CasClientRedirectActionBuilder oidcCasClientRedirectActionBuilder(
@Qualifier("oidcRequestSupport") final OidcRequestSupport oidcRequestSupport,
@Qualifier("oauthCasClient") final Client oauthCasClient
) {
- val builder = new CustomOidcCasClientRedirectActionBuilder(oidcRequestSupport, oauthRequestParameterResolver);
- val casClient = (CasClient) oauthCasClient;
- casClient.setRedirectionActionBuilder((webContext, sessionStore) -> builder.build(casClient, webContext));
+ final var builder = new CustomOidcCasClientRedirectActionBuilder(
+ oidcRequestSupport,
+ oauthRequestParameterResolver
+ );
+ final var casClient = (CasClient) oauthCasClient;
+ casClient.setRedirectionActionBuilder(callContext -> builder.build(casClient, callContext.webContext()));
return builder;
}
@@ -94,7 +118,7 @@ public CorsConfigurationSource corsHttpWebRequestConfigurationSource(
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
- public FilterRegistrationBean casCorsFilter(
+ public CorsFilter corsFilter(
final CasConfigurationProperties casProperties,
@Qualifier(
"corsHttpWebRequestConfigurationSource"
@@ -102,14 +126,80 @@ public FilterRegistrationBean casCorsFilter(
final IdentityProviderHelper identityProviderHelper,
final ProvidersService providersService
) {
- val filter = new CorsFilter(corsHttpWebRequestConfigurationSource);
- // CUSTO:
+ final var filter = new CorsFilter(corsHttpWebRequestConfigurationSource);
filter.setCorsProcessor(new CustomCorsProcessor(providersService, identityProviderHelper));
- val bean = new FilterRegistrationBean<>(filter);
- bean.setName("casCorsFilter");
- bean.setAsyncSupported(true);
- bean.setOrder(0);
- bean.setEnabled(casProperties.getHttpWebRequest().getCors().isEnabled());
- return bean;
+ return filter;
+ }
+
+ @Bean
+ public ResetPasswordController resetPasswordController(
+ @Qualifier(PasswordResetUrlBuilder.BEAN_NAME) final PasswordResetUrlBuilder passwordResetUrlBuilder,
+ @Qualifier(CommunicationsManager.BEAN_NAME) final CommunicationsManager communicationsManager,
+ @Qualifier(
+ PasswordManagementService.DEFAULT_BEAN_NAME
+ ) final PasswordManagementService passwordManagementService,
+ @Qualifier("messageSource") final HierarchicalMessageSource messageSource,
+ final CasConfigurationProperties casProperties,
+ final IdentityProviderHelper identityProviderHelper,
+ final ProvidersService providersService,
+ final Utils utils
+ ) {
+ return new ResetPasswordController(
+ casProperties,
+ passwordManagementService,
+ communicationsManager,
+ messageSource,
+ utils,
+ passwordResetUrlBuilder,
+ identityProviderHelper,
+ providersService,
+ new ObjectMapper()
+ );
+ }
+
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ @Bean
+ public OidcRevocationEndpointController oidcRevocationEndpointController(
+ @Qualifier(OidcConfigurationContext.BEAN_NAME) final OidcConfigurationContext oidcConfigurationContext
+ ) {
+ return new CustomOidcRevocationEndpointController(oidcConfigurationContext);
+ }
+
+ @Bean
+ public WebSecurityCustomizer casWebSecurityCustomizer(
+ @Qualifier("securityContextRepository") final SecurityContextRepository securityContextRepository,
+ final ObjectProvider pathMappedEndpoints,
+ final List configurersList,
+ final WebEndpointProperties webEndpointProperties,
+ final CasConfigurationProperties casProperties
+ ) {
+ val adapter = new CustomCasWebSecurityConfigurerAdapter(
+ casProperties,
+ webEndpointProperties,
+ pathMappedEndpoints,
+ configurersList,
+ securityContextRepository
+ );
+ return adapter::configureWebSecurity;
+ }
+
+ @Bean
+ public SecurityFilterChain casWebSecurityConfigurerAdapter(
+ @Qualifier("securityContextRepository") final SecurityContextRepository securityContextRepository,
+ final HttpSecurity http,
+ final ObjectProvider pathMappedEndpoints,
+ final List configurersList,
+ final WebEndpointProperties webEndpointProperties,
+ final SecurityProperties securityProperties,
+ final CasConfigurationProperties casProperties
+ ) throws Exception {
+ val adapter = new CustomCasWebSecurityConfigurerAdapter(
+ casProperties,
+ webEndpointProperties,
+ pathMappedEndpoints,
+ configurersList,
+ securityContextRepository
+ );
+ return adapter.configureHttpSecurity(http).build();
}
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java
index 9e3c5214dc9..bd0c06f88e1 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java
@@ -36,30 +36,30 @@
*/
package fr.gouv.vitamui.cas.config;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import fr.gouv.vitamui.cas.pm.PmTransientSessionTicketExpirationPolicyBuilder;
-import fr.gouv.vitamui.cas.pm.ResetPasswordController;
-import fr.gouv.vitamui.cas.provider.ProvidersService;
+import fr.gouv.vitamui.cas.delegation.ProvidersService;
+import fr.gouv.vitamui.cas.logout.CustomDelegatedAuthenticationClientLogoutAction;
+import fr.gouv.vitamui.cas.logout.TerminateApiSessionAction;
+import fr.gouv.vitamui.cas.password.PmTransientSessionTicketExpirationPolicyBuilder;
import fr.gouv.vitamui.cas.util.Utils;
-import fr.gouv.vitamui.cas.web.CustomOidcRevocationEndpointController;
-import fr.gouv.vitamui.cas.webflow.actions.CheckMfaTokenAction;
-import fr.gouv.vitamui.cas.webflow.actions.CustomDelegatedAuthenticationClientLogoutAction;
-import fr.gouv.vitamui.cas.webflow.actions.CustomDelegatedClientAuthenticationAction;
-import fr.gouv.vitamui.cas.webflow.actions.CustomSendTokenAction;
-import fr.gouv.vitamui.cas.webflow.actions.CustomerSelectedAction;
-import fr.gouv.vitamui.cas.webflow.actions.DispatcherAction;
-import fr.gouv.vitamui.cas.webflow.actions.GeneralTerminateSessionAction;
-import fr.gouv.vitamui.cas.webflow.actions.I18NSendPasswordResetInstructionsAction;
-import fr.gouv.vitamui.cas.webflow.actions.ListCustomersAction;
-import fr.gouv.vitamui.cas.webflow.actions.TriggerChangePasswordAction;
-import fr.gouv.vitamui.cas.webflow.configurer.CustomCasSimpleMultifactorWebflowConfigurer;
-import fr.gouv.vitamui.cas.webflow.configurer.CustomLoginWebflowConfigurer;
-import fr.gouv.vitamui.cas.webflow.resolver.CustomCasDelegatingWebflowEventResolver;
+import fr.gouv.vitamui.cas.webflow.login.VitamLoginWebflowConfigurer;
+import fr.gouv.vitamui.cas.webflow.login.actions.CheckSubrogationAction;
+import fr.gouv.vitamui.cas.webflow.login.actions.CustomDelegatedClientAuthenticationAction;
+import fr.gouv.vitamui.cas.webflow.login.actions.CustomerSelectedAction;
+import fr.gouv.vitamui.cas.webflow.login.actions.DispatcherAction;
+import fr.gouv.vitamui.cas.webflow.login.actions.I18NSendPasswordResetInstructionsAction;
+import fr.gouv.vitamui.cas.webflow.login.actions.ListCustomersAction;
+import fr.gouv.vitamui.cas.webflow.login.actions.TriggerChangePasswordAction;
+import fr.gouv.vitamui.cas.webflow.mfa.VitamMfaWebflowConfigurer;
+import fr.gouv.vitamui.cas.webflow.mfa.actions.CheckMfaTokenAction;
+import fr.gouv.vitamui.cas.webflow.mfa.actions.CustomSendTokenAction;
import fr.gouv.vitamui.cas.x509.CustomRequestHeaderX509CertificateExtractor;
-import fr.gouv.vitamui.iam.client.CasRestClient;
+import fr.gouv.vitamui.cas.x509.X509CasDelegatingWebflowEventResolver;
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
+import fr.gouv.vitamui.iam.openapiclient.CasApi;
import lombok.val;
import org.apereo.cas.CentralAuthenticationService;
+import org.apereo.cas.authentication.AuthenticationSystemSupport;
+import org.apereo.cas.authentication.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.bucket4j.consumer.BucketConsumer;
@@ -67,17 +67,13 @@
import org.apereo.cas.logout.LogoutManager;
import org.apereo.cas.logout.slo.SingleLogoutRequestExecutor;
import org.apereo.cas.mfa.simple.CasSimpleMultifactorTokenCommunicationStrategy;
-import org.apereo.cas.mfa.simple.ticket.CasSimpleMultifactorAuthenticationTicketFactory;
import org.apereo.cas.mfa.simple.validation.CasSimpleMultifactorAuthenticationService;
import org.apereo.cas.notifications.CommunicationsManager;
-import org.apereo.cas.oidc.OidcConfigurationContext;
-import org.apereo.cas.oidc.web.controllers.token.OidcRevocationEndpointController;
import org.apereo.cas.pac4j.client.DelegatedClientAuthenticationFailureEvaluator;
+import org.apereo.cas.pac4j.client.DelegatedIdentityProviders;
import org.apereo.cas.pm.PasswordManagementService;
import org.apereo.cas.pm.PasswordResetUrlBuilder;
import org.apereo.cas.services.ServicesManager;
-import org.apereo.cas.ticket.ServiceTicketSessionTrackingPolicy;
-import org.apereo.cas.ticket.TicketFactory;
import org.apereo.cas.ticket.TransientSessionTicket;
import org.apereo.cas.ticket.factory.DefaultTicketFactory;
import org.apereo.cas.ticket.factory.DefaultTransientSessionTicketFactory;
@@ -98,10 +94,8 @@
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.impl.CasWebflowEventResolutionConfigurationContext;
import org.apereo.cas.web.flow.util.MultifactorAuthenticationWebflowUtils;
-import org.pac4j.core.client.Clients;
import org.pac4j.core.context.session.SessionStore;
import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -124,103 +118,13 @@
@Configuration
public class WebflowConfig {
- @Autowired
- private CasConfigurationProperties casProperties;
-
- @Autowired
- private ProvidersService providersService;
-
- @Autowired
- private IdentityProviderHelper identityProviderHelper;
-
- @Autowired
- private FlowBuilderServices flowBuilderServices;
-
- @Autowired
- @Qualifier("logoutFlowRegistry")
- private FlowDefinitionRegistry logoutFlowDefinitionRegistry;
-
- @Autowired
- @Qualifier("loginFlowRegistry")
- private FlowDefinitionRegistry loginFlowDefinitionRegistry;
-
- @Autowired
- private ConfigurableApplicationContext applicationContext;
-
- @Autowired
- private CasRestClient casRestClient;
-
- @Autowired
- private TicketRegistry ticketRegistry;
-
- @Autowired
- @Qualifier("centralAuthenticationService")
- private ObjectProvider centralAuthenticationService;
-
- @Autowired
- @Qualifier("delegatedClientDistributedSessionStore")
- private ObjectProvider delegatedClientDistributedSessionStore;
-
- @Autowired
- private Utils utils;
-
- @Autowired
- private TicketRegistrySupport ticketRegistrySupport;
-
- @Autowired
- @Qualifier("messageSource")
- private HierarchicalMessageSource messageSource;
-
- @Autowired
- @Qualifier("casSimpleMultifactorAuthenticationTicketFactory")
- private CasSimpleMultifactorAuthenticationTicketFactory casSimpleMultifactorAuthenticationTicketFactory;
-
- @Autowired
- private LogoutManager logoutManager;
-
- @Autowired
- @Qualifier("mfaSimpleMultifactorTokenCommunicationStrategy")
- private CasSimpleMultifactorTokenCommunicationStrategy mfaSimpleMultifactorTokenCommunicationStrategy;
-
- @Autowired
- @Qualifier("mfaSimpleAuthenticatorFlowRegistry")
- private FlowDefinitionRegistry mfaSimpleAuthenticatorFlowRegistry;
-
- @Autowired
- @Qualifier("servicesManager")
- private ServicesManager servicesManager;
-
- @Autowired
- @Qualifier("frontChannelLogoutAction")
- private Action frontChannelLogoutAction;
-
- @Autowired
- @Qualifier("adaptiveAuthenticationPolicy")
- private ObjectProvider adaptiveAuthenticationPolicy;
-
- @Autowired
- @Qualifier("serviceTicketRequestWebflowEventResolver")
- private ObjectProvider serviceTicketRequestWebflowEventResolver;
-
- @Autowired
- @Qualifier("initialAuthenticationAttemptWebflowEventResolver")
- private ObjectProvider initialAuthenticationAttemptWebflowEventResolver;
-
- @Value("${vitamui.portal.url}")
- private String vitamuiPortalUrl;
-
- @Value("${theme.vitamui-platform-name:VITAM-UI}")
- private String vitamuiPlatformName;
-
- @Value("${vitamui.authn.x509.enabled:false}")
- private boolean x509AuthnEnabled;
-
- @Value("${vitamui.authn.x509.mandatory:false}")
- private boolean x509AuthnMandatory;
-
@Bean
- public ListCustomersAction listCustomersAction() {
- return new ListCustomersAction(providersService, identityProviderHelper, casRestClient, utils);
+ public ListCustomersAction listCustomersAction(
+ ProvidersService providersService,
+ IdentityProviderHelper identityProviderHelper,
+ CasApi casApi
+ ) {
+ return new ListCustomersAction(providersService, identityProviderHelper, casApi);
}
@Bean
@@ -229,26 +133,44 @@ public CustomerSelectedAction customerSelectedAction() {
}
@Bean
- public DispatcherAction dispatcherAction() {
+ public DispatcherAction dispatcherAction(
+ ProvidersService providersService,
+ IdentityProviderHelper identityProviderHelper,
+ CasApi casApi,
+ Utils utils,
+ @Qualifier("delegatedClientDistributedSessionStore") ObjectProvider<
+ SessionStore
+ > delegatedClientDistributedSessionStore
+ ) {
return new DispatcherAction(
providersService,
identityProviderHelper,
- casRestClient,
+ casApi,
utils,
delegatedClientDistributedSessionStore.getObject()
);
}
@Bean
- public DefaultTransientSessionTicketFactory pmTicketFactory() {
+ public DefaultTransientSessionTicketFactory pmTicketFactory(final CasConfigurationProperties casProperties) {
return new DefaultTransientSessionTicketFactory(
new PmTransientSessionTicketExpirationPolicyBuilder(casProperties)
);
}
+ @Bean
+ public Action checkSubrogationAction(final CasApi casApi) {
+ return new CheckSubrogationAction(casApi);
+ }
+
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public Action sendPasswordResetInstructionsAction(
+ @Qualifier(AuthenticationSystemSupport.BEAN_NAME) final AuthenticationSystemSupport authenticationSystemSupport,
+ @Qualifier(
+ MultifactorAuthenticationProviderSelector.BEAN_NAME
+ ) final MultifactorAuthenticationProviderSelector multifactorAuthenticationProviderSelector,
+ final ConfigurableApplicationContext applicationContext,
final CasConfigurationProperties casProperties,
@Qualifier(
PasswordManagementService.DEFAULT_BEAN_NAME
@@ -256,11 +178,15 @@ public Action sendPasswordResetInstructionsAction(
@Qualifier(TicketRegistry.BEAN_NAME) final TicketRegistry ticketRegistry,
@Qualifier(PrincipalResolver.BEAN_NAME_PRINCIPAL_RESOLVER) final PrincipalResolver defaultPrincipalResolver,
@Qualifier(CommunicationsManager.BEAN_NAME) final CommunicationsManager communicationsManager,
- @Qualifier(TicketFactory.BEAN_NAME) final TicketFactory ticketFactory,
- @Qualifier(PasswordResetUrlBuilder.BEAN_NAME) final PasswordResetUrlBuilder passwordResetUrlBuilder
+ @Qualifier(PasswordResetUrlBuilder.BEAN_NAME) final PasswordResetUrlBuilder passwordResetUrlBuilder,
+ final ProvidersService providersService,
+ final IdentityProviderHelper identityProviderHelper,
+ final Utils utils,
+ @Qualifier("messageSource") final HierarchicalMessageSource messageSource,
+ @Value("${theme.vitamui-platform-name:VITAM-UI}") final String vitamuiPlatformName
) {
- val pmTicketFactory = new DefaultTicketFactory();
- pmTicketFactory.addTicketFactory(TransientSessionTicket.class, pmTicketFactory());
+ final var pmTicketFactory = new DefaultTicketFactory();
+ pmTicketFactory.addTicketFactory(TransientSessionTicket.class, pmTicketFactory(casProperties));
return new I18NSendPasswordResetInstructionsAction(
casProperties,
@@ -270,6 +196,9 @@ public Action sendPasswordResetInstructionsAction(
pmTicketFactory,
defaultPrincipalResolver,
passwordResetUrlBuilder,
+ multifactorAuthenticationProviderSelector,
+ authenticationSystemSupport,
+ applicationContext,
messageSource,
providersService,
identityProviderHelper,
@@ -279,7 +208,10 @@ public Action sendPasswordResetInstructionsAction(
}
@Bean
- public TriggerChangePasswordAction triggerChangePasswordAction() {
+ public TriggerChangePasswordAction triggerChangePasswordAction(
+ TicketRegistrySupport ticketRegistrySupport,
+ Utils utils
+ ) {
return new TriggerChangePasswordAction(ticketRegistrySupport, utils);
}
@@ -297,14 +229,14 @@ public CasWebflowConfigurer defaultWebflowConfigurer(
) final FlowDefinitionRegistry logoutFlowRegistry,
@Qualifier(CasWebflowConstants.BEAN_NAME_FLOW_BUILDER_SERVICES) final FlowBuilderServices flowBuilderServices
) {
- val c = new CustomLoginWebflowConfigurer(
+ final var c = new VitamLoginWebflowConfigurer(
flowBuilderServices,
loginFlowRegistry,
applicationContext,
casProperties
);
c.setLogoutFlowDefinitionRegistry(logoutFlowRegistry);
- c.setOrder(Ordered.HIGHEST_PRECEDENCE);
+ c.setOrder(10);
return c;
}
@@ -317,11 +249,18 @@ public Action delegatedAuthenticationAction(
DelegatedClientAuthenticationFailureEvaluator.BEAN_NAME
) final DelegatedClientAuthenticationFailureEvaluator delegatedClientAuthenticationFailureEvaluator,
@Qualifier(
- DelegatedClientAuthenticationConfigurationContext.DEFAULT_BEAN_NAME
+ DelegatedClientAuthenticationConfigurationContext.BEAN_NAME
) final DelegatedClientAuthenticationConfigurationContext delegatedClientAuthenticationConfigurationContext,
@Qualifier(
DelegatedClientAuthenticationWebflowManager.DEFAULT_BEAN_NAME
- ) final DelegatedClientAuthenticationWebflowManager delegatedClientWebflowManager
+ ) final DelegatedClientAuthenticationWebflowManager delegatedClientWebflowManager,
+ final ProvidersService providersService,
+ final IdentityProviderHelper identityProviderHelper,
+ final TicketRegistry ticketRegistry,
+ final CasApi casApi,
+ final Utils utils,
+ @Value("${vitamui.portal.url}") final String vitamuiPortalUrl
+ // ,@Value("${cas.authn.surrogate.separator}") final String surrogationSeparator
) {
return WebflowActionBeanSupplier.builder()
.withApplicationContext(applicationContext)
@@ -336,7 +275,7 @@ public Action delegatedAuthenticationAction(
providersService,
utils,
ticketRegistry,
- casRestClient,
+ casApi,
vitamuiPortalUrl
)
)
@@ -358,33 +297,34 @@ public Action terminateSessionAction(
@Qualifier(
CentralAuthenticationService.BEAN_NAME
) final CentralAuthenticationService centralAuthenticationService,
+ final Utils utils,
+ final CasApi casApi,
+ final ServicesManager servicesManager,
+ final TicketRegistry ticketRegistry,
+ @Qualifier(CasWebflowConstants.ACTION_ID_FRONT_CHANNEL_LOGOUT) final Action frontChannelLogoutAction,
@Qualifier(
SingleLogoutRequestExecutor.BEAN_NAME
- ) final SingleLogoutRequestExecutor defaultSingleLogoutRequestExecutor,
- @Qualifier(
- ServiceTicketSessionTrackingPolicy.BEAN_NAME
- ) final ServiceTicketSessionTrackingPolicy serviceTicketSessionTrackingPolicy
+ ) final SingleLogoutRequestExecutor defaultSingleLogoutRequestExecutor
) {
return WebflowActionBeanSupplier.builder()
.withApplicationContext(applicationContext)
.withProperties(casProperties)
.withAction(
() ->
- new GeneralTerminateSessionAction(
+ new TerminateApiSessionAction(
centralAuthenticationService,
ticketGrantingTicketCookieGenerator,
warnCookieGenerator,
casProperties.getLogout(),
logoutManager,
applicationContext,
- defaultSingleLogoutRequestExecutor,
utils,
- casRestClient,
+ casApi,
servicesManager,
casProperties,
frontChannelLogoutAction,
ticketRegistry,
- serviceTicketSessionTrackingPolicy
+ defaultSingleLogoutRequestExecutor
)
)
.withId(CasWebflowConstants.ACTION_ID_TERMINATE_SESSION)
@@ -392,29 +332,6 @@ public Action terminateSessionAction(
.get();
}
- @Bean
- public ResetPasswordController resetPasswordController(
- @Qualifier(PasswordResetUrlBuilder.BEAN_NAME) final PasswordResetUrlBuilder passwordResetUrlBuilder,
- final IdentityProviderHelper identityProviderHelper,
- final ProvidersService providersService,
- @Qualifier(CommunicationsManager.BEAN_NAME) final CommunicationsManager communicationsManager,
- @Qualifier(
- PasswordManagementService.DEFAULT_BEAN_NAME
- ) final PasswordManagementService passwordManagementService
- ) {
- return new ResetPasswordController(
- casProperties,
- passwordManagementService,
- communicationsManager,
- messageSource,
- utils,
- passwordResetUrlBuilder,
- identityProviderHelper,
- providersService,
- new ObjectMapper()
- );
- }
-
@Bean
public Action loadSurrogatesListAction() {
return StaticEventExecutionAction.SUCCESS;
@@ -430,15 +347,16 @@ public Action mfaSimpleMultifactorSendTokenAction(
@Qualifier(
"mfaSimpleMultifactorTokenCommunicationStrategy"
) final CasSimpleMultifactorTokenCommunicationStrategy mfaSimpleMultifactorTokenCommunicationStrategy,
- final CasConfigurationProperties casProperties,
@Qualifier(CommunicationsManager.BEAN_NAME) final CommunicationsManager communicationsManager,
- @Qualifier("mfaSimpleMultifactorBucketConsumer") final BucketConsumer mfaSimpleMultifactorBucketConsumer
+ @Qualifier("mfaSimpleMultifactorBucketConsumer") final BucketConsumer mfaSimpleMultifactorBucketConsumer,
+ final CasConfigurationProperties casProperties,
+ final Utils utils
) {
return WebflowActionBeanSupplier.builder()
.withApplicationContext(applicationContext)
.withProperties(casProperties)
.withAction(() -> {
- val simple = casProperties.getAuthn().getMfa().getSimple();
+ var simple = casProperties.getAuthn().getMfa().getSimple();
return new CustomSendTokenAction(
communicationsManager,
casSimpleMultifactorAuthenticationService,
@@ -455,10 +373,21 @@ public Action mfaSimpleMultifactorSendTokenAction(
@Bean
@DependsOn("defaultWebflowConfigurer")
- public CasWebflowConfigurer mfaSimpleMultifactorWebflowConfigurer() {
- val cfg = new CustomCasSimpleMultifactorWebflowConfigurer(
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ public CasWebflowConfigurer mfaSimpleMultifactorWebflowConfigurer(
+ @Qualifier(
+ "mfaSimpleAuthenticatorFlowRegistry"
+ ) final FlowDefinitionRegistry mfaSimpleAuthenticatorFlowRegistry,
+ @Qualifier(
+ CasWebflowConstants.BEAN_NAME_LOGIN_FLOW_DEFINITION_REGISTRY
+ ) final FlowDefinitionRegistry loginFlowRegistry,
+ @Qualifier(CasWebflowConstants.BEAN_NAME_FLOW_BUILDER_SERVICES) final FlowBuilderServices flowBuilderServices,
+ final CasConfigurationProperties casProperties,
+ final ConfigurableApplicationContext applicationContext
+ ) {
+ final var cfg = new VitamMfaWebflowConfigurer(
flowBuilderServices,
- loginFlowDefinitionRegistry,
+ loginFlowRegistry,
mfaSimpleAuthenticatorFlowRegistry,
applicationContext,
casProperties,
@@ -469,7 +398,7 @@ public CasWebflowConfigurer mfaSimpleMultifactorWebflowConfigurer() {
}
@Bean
- public Action checkMfaTokenAction() {
+ public Action checkMfaTokenAction(final TicketRegistry ticketRegistry) {
return new CheckMfaTokenAction(ticketRegistry);
}
@@ -478,10 +407,10 @@ public Action checkMfaTokenAction() {
public Action delegatedAuthenticationClientLogoutAction(
final CasConfigurationProperties casProperties,
final ConfigurableApplicationContext applicationContext,
- @Qualifier("builtClients") final Clients builtClients,
+ @Qualifier(DelegatedIdentityProviders.BEAN_NAME) final DelegatedIdentityProviders identityProviders,
@Qualifier("delegatedClientDistributedSessionStore") final SessionStore delegatedClientDistributedSessionStore,
- final IdentityProviderHelper identityProviderHelper,
- final ProvidersService providersService
+ final ProvidersService providersService,
+ final IdentityProviderHelper identityProviderHelper
) {
return BeanSupplier.of(Action.class)
.when(
@@ -498,7 +427,7 @@ public Action delegatedAuthenticationClientLogoutAction(
.withAction(
() ->
new CustomDelegatedAuthenticationClientLogoutAction(
- builtClients,
+ identityProviders,
delegatedClientDistributedSessionStore,
providersService,
identityProviderHelper
@@ -514,7 +443,18 @@ public Action delegatedAuthenticationClientLogoutAction(
@Bean
@RefreshScope
- public Action x509Check() {
+ public Action x509Check(
+ final CasConfigurationProperties casProperties,
+ @Qualifier("adaptiveAuthenticationPolicy") final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
+ @Qualifier(
+ "serviceTicketRequestWebflowEventResolver"
+ ) final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
+ @Qualifier(
+ "initialAuthenticationAttemptWebflowEventResolver"
+ ) final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
+ @Value("${vitamui.authn.x509.enabled:false}") final boolean x509AuthnEnabled,
+ @Value("${vitamui.authn.x509.mandatory:false}") final boolean x509AuthnMandatory
+ ) {
if (x509AuthnEnabled) {
val sslHeaderName = casProperties.getAuthn().getX509().getSslHeaderName();
val certificateExtractor = new CustomRequestHeaderX509CertificateExtractor(
@@ -523,9 +463,9 @@ public Action x509Check() {
);
return new X509CertificateCredentialsRequestHeaderAction(
- initialAuthenticationAttemptWebflowEventResolver.getObject(),
- serviceTicketRequestWebflowEventResolver.getObject(),
- adaptiveAuthenticationPolicy.getObject(),
+ initialAuthenticationAttemptWebflowEventResolver,
+ serviceTicketRequestWebflowEventResolver,
+ adaptiveAuthenticationPolicy,
certificateExtractor,
casProperties
);
@@ -558,9 +498,9 @@ public CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEven
@Qualifier(
"restEndpointAuthenticationPolicyWebflowEventResolver"
) final CasWebflowEventResolver restEndpointAuthenticationPolicyWebflowEventResolver,
- @Qualifier(
- "groovyScriptAuthenticationPolicyWebflowEventResolver"
- ) final CasWebflowEventResolver groovyScriptAuthenticationPolicyWebflowEventResolver,
+ @Qualifier("groovyScriptAuthenticationPolicyWebflowEventResolver") final ObjectProvider<
+ CasWebflowEventResolver
+ > groovyScriptAuthenticationPolicyWebflowEventResolver,
@Qualifier(
"scriptedRegisteredServiceAuthenticationPolicyWebflowEventResolver"
) final CasWebflowEventResolver scriptedRegisteredServiceAuthenticationPolicyWebflowEventResolver,
@@ -578,9 +518,10 @@ public CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEven
) final CasWebflowEventResolver authenticationAttributeAuthenticationPolicyWebflowEventResolver,
@Qualifier(
"registeredServiceAuthenticationPolicyWebflowEventResolver"
- ) final CasWebflowEventResolver registeredServiceAuthenticationPolicyWebflowEventResolver
+ ) final CasWebflowEventResolver registeredServiceAuthenticationPolicyWebflowEventResolver,
+ @Value("${vitamui.authn.x509.mandatory:false}") final boolean x509AuthnMandatory
) {
- val resolver = new CustomCasDelegatingWebflowEventResolver(
+ final var resolver = new X509CasDelegatingWebflowEventResolver(
casWebflowConfigurationContext,
selectiveAuthenticationProviderWebflowEventResolver,
x509AuthnMandatory
@@ -590,7 +531,7 @@ public CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEven
resolver.addDelegate(globalAuthenticationPolicyWebflowEventResolver);
resolver.addDelegate(httpRequestAuthenticationPolicyWebflowEventResolver);
resolver.addDelegate(restEndpointAuthenticationPolicyWebflowEventResolver);
- resolver.addDelegate(groovyScriptAuthenticationPolicyWebflowEventResolver);
+ groovyScriptAuthenticationPolicyWebflowEventResolver.ifAvailable(resolver::addDelegate);
resolver.addDelegate(scriptedRegisteredServiceAuthenticationPolicyWebflowEventResolver);
resolver.addDelegate(registeredServicePrincipalAttributeAuthenticationPolicyWebflowEventResolver);
resolver.addDelegate(predicatedPrincipalAttributeMultifactorAuthenticationPolicyEventResolver);
@@ -600,18 +541,10 @@ public CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEven
return resolver;
}
- @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
- @Bean
- public OidcRevocationEndpointController oidcRevocationEndpointController(
- @Qualifier(OidcConfigurationContext.BEAN_NAME) final OidcConfigurationContext oidcConfigurationContext
- ) {
- return new CustomOidcRevocationEndpointController(oidcConfigurationContext);
- }
-
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = CasWebflowConstants.ACTION_ID_SURROGATE_INITIAL_AUTHENTICATION)
- public Action surrogateInitialAuthenticationAction(final CasConfigurationProperties casProperties) {
+ public Action surrogateInitialAuthenticationAction() {
return new CustomSurrogateInitialAuthenticationAction();
}
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/CustomDelegatedIdentityProviders.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/CustomDelegatedIdentityProviders.java
new file mode 100644
index 00000000000..b6dcaedad43
--- /dev/null
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/CustomDelegatedIdentityProviders.java
@@ -0,0 +1,55 @@
+/**
+ * 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.delegation;
+
+import lombok.RequiredArgsConstructor;
+import org.apereo.cas.pac4j.client.DelegatedIdentityProviders;
+import org.pac4j.core.client.Client;
+
+import java.util.List;
+import java.util.Optional;
+
+/** Wrapper of the ProvidersService for the CAS DelegatedIdentityProviders */
+@RequiredArgsConstructor
+public class CustomDelegatedIdentityProviders implements DelegatedIdentityProviders {
+
+ private final ProvidersService providerService;
+
+ @Override
+ public List findAllClients() {
+ return providerService.getClients().findAllClients();
+ }
+
+ @Override
+ public Optional findClient(final String name) {
+ return providerService.getClients().findClient(name);
+ }
+}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/Pac4jClientIdentityProviderDto.java
similarity index 99%
rename from cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java
rename to cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/Pac4jClientIdentityProviderDto.java
index 04b2908fe36..469e0590a71 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/Pac4jClientIdentityProviderDto.java
@@ -34,7 +34,7 @@
* 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.provider;
+package fr.gouv.vitamui.cas.delegation;
import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto;
import org.pac4j.core.client.IndirectClient;
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/ProvidersService.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/ProvidersService.java
similarity index 85%
rename from cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/ProvidersService.java
rename to cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/ProvidersService.java
index a244758c3ef..1e5c74ff94f 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/ProvidersService.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/delegation/ProvidersService.java
@@ -34,29 +34,26 @@
* 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.provider;
+package fr.gouv.vitamui.cas.delegation;
-import fr.gouv.vitamui.cas.util.Utils;
-import fr.gouv.vitamui.iam.client.IdentityProviderRestClient;
import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto;
import fr.gouv.vitamui.iam.common.dto.common.ProviderEmbeddedOptions;
import fr.gouv.vitamui.iam.common.utils.Pac4jClientBuilder;
+import fr.gouv.vitamui.iam.openapiclient.IdentityProvidersApi;
+import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.client.IndirectClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.Assert;
-import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
-import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -64,22 +61,21 @@
*
*
*/
-@Getter
+
+@Slf4j
@RequiredArgsConstructor
public class ProvidersService {
- private static final Logger LOGGER = LoggerFactory.getLogger(ProvidersService.class);
-
+ @Getter
private List providers = new ArrayList<>();
+ @Getter
private final Clients clients;
- private final IdentityProviderRestClient identityProviderRestClient;
+ private final IdentityProvidersApi identityProvidersApi;
private final Pac4jClientBuilder pac4jClientBuilder;
- private final Utils utils;
-
@PostConstruct
public void afterPropertiesSet() {
loadData();
@@ -97,11 +93,9 @@ public void reloadData() {
}
protected void loadData() {
- final List temporaryProviders = identityProviderRestClient.getAll(
- utils.buildContext(null),
- Optional.empty(),
- Optional.of(ProviderEmbeddedOptions.KEYSTORE + "," + ProviderEmbeddedOptions.IDPMETADATA)
- );
+ final String embedded = ProviderEmbeddedOptions.KEYSTORE + "," + ProviderEmbeddedOptions.IDPMETADATA;
+ List temporaryProviders =
+ identityProvidersApi.getAll(null, embedded);
// sort by identifier. This is needed in order to take the internal provider first.
temporaryProviders.sort(Comparator.comparing(IdentityProviderDto::getIdentifier));
LOGGER.debug(
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/CustomDelegatedAuthenticationClientLogoutAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/CustomDelegatedAuthenticationClientLogoutAction.java
new file mode 100644
index 00000000000..3c9d08059b8
--- /dev/null
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/CustomDelegatedAuthenticationClientLogoutAction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2022)
+ *
+ * contact.vitam@culture.gouv.fr
+ *
+ * This software is a computer program whose purpose is to implement a digital archiving back-office system managing
+ * high volumetry securely and efficiently.
+ *
+ * This software is governed by the CeCILL 2.1 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 2.1 license as
+ * circulated by CEA, CNRS and INRIA at the following URL "https://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 2.1 license and that you
+ * accept its terms.
+ */
+
+package fr.gouv.vitamui.cas.logout;
+
+import fr.gouv.vitamui.cas.delegation.ProvidersService;
+import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.apereo.cas.pac4j.client.DelegatedIdentityProviders;
+import org.apereo.cas.web.flow.actions.logout.DelegatedAuthenticationClientLogoutAction;
+import org.pac4j.core.client.Client;
+import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.profile.UserProfile;
+
+import java.util.Optional;
+
+/**
+ * Propagate the logout from CAS to the authn delegated server.
+ */
+@Slf4j
+public class CustomDelegatedAuthenticationClientLogoutAction extends DelegatedAuthenticationClientLogoutAction {
+
+ private final ProvidersService providersService;
+
+ private final IdentityProviderHelper identityProviderHelper;
+
+ public CustomDelegatedAuthenticationClientLogoutAction(
+ final DelegatedIdentityProviders identityProviders,
+ final SessionStore sessionStore,
+ final ProvidersService providersService,
+ final IdentityProviderHelper identityProviderHelper
+ ) {
+ super(identityProviders, sessionStore);
+ this.providersService = providersService;
+ this.identityProviderHelper = identityProviderHelper;
+ }
+
+ @Override
+ protected Optional findCurrentClient(final UserProfile currentProfile) {
+ val optClient = currentProfile == null
+ ? Optional.empty()
+ : identityProviders.findClient(currentProfile.getClientName());
+
+ LOGGER.debug("optClient: {}", optClient);
+ if (optClient.isEmpty()) {
+ return Optional.empty();
+ }
+
+ val client = optClient.get();
+ val provider = identityProviderHelper
+ .findByTechnicalName(providersService.getProviders(), client.getName())
+ .get();
+ LOGGER.debug("provider: {}", provider);
+ if (!provider.isPropagateLogout()) {
+ return Optional.empty();
+ }
+
+ return optClient;
+ }
+}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/TerminateApiSessionAction.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/TerminateApiSessionAction.java
new file mode 100644
index 00000000000..1509b4c2a78
--- /dev/null
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/TerminateApiSessionAction.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2022)
+ *
+ * contact.vitam@culture.gouv.fr
+ *
+ * This software is a computer program whose purpose is to implement a digital archiving back-office system managing
+ * high volumetry securely and efficiently.
+ *
+ * This software is governed by the CeCILL 2.1 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 2.1 license as
+ * circulated by CEA, CNRS and INRIA at the following URL "https://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 2.1 license and that you
+ * accept its terms.
+ */
+package fr.gouv.vitamui.cas.logout;
+
+import fr.gouv.vitamui.cas.util.Utils;
+import fr.gouv.vitamui.iam.openapiclient.CasApi;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apereo.cas.CentralAuthenticationService;
+import org.apereo.cas.authentication.Authentication;
+import org.apereo.cas.authentication.AuthenticationResult;
+import org.apereo.cas.authentication.DefaultAuthenticationBuilder;
+import org.apereo.cas.authentication.DefaultAuthenticationResultBuilder;
+import org.apereo.cas.authentication.principal.DefaultPrincipalElectionStrategy;
+import org.apereo.cas.authentication.principal.Principal;
+import org.apereo.cas.authentication.principal.WebApplicationService;
+import org.apereo.cas.authentication.principal.WebApplicationServiceFactory;
+import org.apereo.cas.configuration.CasConfigurationProperties;
+import org.apereo.cas.configuration.model.core.logout.LogoutProperties;
+import org.apereo.cas.logout.LogoutManager;
+import org.apereo.cas.logout.slo.SingleLogoutExecutionRequest;
+import org.apereo.cas.logout.slo.SingleLogoutRequestContext;
+import org.apereo.cas.logout.slo.SingleLogoutRequestExecutor;
+import org.apereo.cas.services.BaseRegisteredService;
+import org.apereo.cas.services.RegisteredService;
+import org.apereo.cas.services.ServicesManager;
+import org.apereo.cas.ticket.InvalidTicketException;
+import org.apereo.cas.ticket.TicketGrantingTicket;
+import org.apereo.cas.ticket.registry.TicketRegistry;
+import org.apereo.cas.web.cookie.CasCookieBuilder;
+import org.apereo.cas.web.flow.logout.TerminateSessionAction;
+import org.apereo.cas.web.support.WebUtils;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.webflow.execution.Action;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static fr.gouv.vitamui.commons.api.CommonConstants.AUTHTOKEN_ATTRIBUTE;
+import static fr.gouv.vitamui.commons.api.CommonConstants.SUPER_USER_ATTRIBUTE;
+import static fr.gouv.vitamui.commons.api.CommonConstants.SUPER_USER_CUSTOMER_ID_ATTRIBUTE;
+
+/** Terminate session action with custom IAM logout call. */
+@Slf4j
+public class TerminateApiSessionAction extends TerminateSessionAction {
+
+ private final Utils utils;
+ private final CasApi casApi;
+ private final ServicesManager servicesManager;
+ private final CasConfigurationProperties casProperties;
+ private final Action frontChannelLogoutAction;
+ private final TicketRegistry ticketRegistry;
+
+ public TerminateApiSessionAction(
+ final CentralAuthenticationService centralAuthenticationService,
+ final CasCookieBuilder ticketGrantingTicketCookieGenerator,
+ final CasCookieBuilder warnCookieGenerator,
+ final LogoutProperties logoutProperties,
+ final LogoutManager logoutManager,
+ final ConfigurableApplicationContext applicationContext,
+ final Utils utils,
+ final CasApi casApi,
+ final ServicesManager servicesManager,
+ final CasConfigurationProperties casProperties,
+ final Action frontChannelLogoutAction,
+ final TicketRegistry ticketRegistry,
+ final SingleLogoutRequestExecutor singleLogoutRequestExecutor
+ ) {
+ super(
+ centralAuthenticationService,
+ ticketGrantingTicketCookieGenerator,
+ warnCookieGenerator,
+ logoutProperties,
+ logoutManager,
+ applicationContext,
+ singleLogoutRequestExecutor
+ );
+ this.utils = utils;
+ this.casApi = casApi;
+ this.servicesManager = servicesManager;
+ this.casProperties = casProperties;
+ this.frontChannelLogoutAction = frontChannelLogoutAction;
+ this.ticketRegistry = ticketRegistry;
+ }
+
+ @Override
+ @SneakyThrows
+ public Event terminate(final RequestContext context) {
+ final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
+ final HttpServletResponse response = WebUtils.getHttpServletResponseFromExternalWebflowContext(context);
+
+ String tgtId = WebUtils.getTicketGrantingTicketId(context);
+ if (StringUtils.isBlank(tgtId)) {
+ tgtId = ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
+ }
+
+ TicketGrantingTicket ticket = null;
+ if (StringUtils.isNotBlank(tgtId)) {
+ try {
+ ticket = ticketRegistry.getTicket(tgtId, TicketGrantingTicket.class);
+ if (ticket != null) {
+ final Principal principal = ticket.getAuthentication().getPrincipal();
+ final Map> attributes = principal.getAttributes();
+ final String authToken = (String) utils.getAttributeValue(attributes, AUTHTOKEN_ATTRIBUTE);
+ final String superUserEmail = Optional.ofNullable(
+ (String) utils.getAttributeValue(attributes, SUPER_USER_ATTRIBUTE)
+ ).orElse("");
+ final String superUserCustomerId = Optional.ofNullable(
+ (String) utils.getAttributeValue(attributes, SUPER_USER_CUSTOMER_ID_ATTRIBUTE)
+ ).orElse("");
+
+ LOGGER.debug(
+ "Calling logout for authToken={} and superUser={}, superUserCustomerId={}",
+ authToken,
+ superUserEmail,
+ superUserCustomerId
+ );
+
+ casApi.logout(authToken, superUserEmail, superUserCustomerId);
+ }
+ } catch (final InvalidTicketException e) {
+ LOGGER.warn("No TGT found for the CAS cookie: {}", tgtId);
+ }
+ }
+
+ final Event event = super.terminate(context);
+
+ // Remove IdP cookie
+ response.addCookie(utils.buildIdpCookie(null, casProperties.getTgc()));
+
+ // Fallback general logout
+ if (tgtId == null || ticket == null || ticket.isExpired()) {
+ List logoutRequests = performGeneralLogout(tgtId != null ? tgtId : "nocookie");
+ WebUtils.putLogoutRequests(context, logoutRequests);
+ }
+
+ // Front channel logout in login flow
+ if ("login".equals(context.getFlowExecutionContext().getDefinition().getId())) {
+ LOGGER.debug("Computing front channel logout URLs");
+ frontChannelLogoutAction.execute(context);
+ }
+
+ return event;
+ }
+
+ protected List performGeneralLogout(final String tgtId) {
+ try {
+ // 1️⃣ Crée l'Authentication factice
+ Authentication fakeAuthentication = new DefaultAuthenticationBuilder()
+ .setPrincipal(new FakePrincipal(tgtId))
+ .build();
+
+ // 2️⃣ Crée un AuthenticationResult factice à partir de l'authentication
+ AuthenticationResult authenticationResult = new DefaultAuthenticationResultBuilder()
+ .collect(fakeAuthentication)
+ .build(new DefaultPrincipalElectionStrategy()); // PrincipalElectionStrategy trivial
+
+ // 3️⃣ Crée le TGT factice via l'API CAS 7
+ TicketGrantingTicket fakeTgt = centralAuthenticationService.createTicketGrantingTicket(
+ authenticationResult
+ );
+
+ Collection registeredServices = servicesManager.getAllServices();
+ // Préparer le factory de services CAS
+ WebApplicationServiceFactory serviceFactory = new WebApplicationServiceFactory();
+
+ for (RegisteredService registeredService : registeredServices) {
+ if (!(registeredService instanceof BaseRegisteredService baseService)) continue;
+
+ String logoutUrl = baseService.getLogoutUrl();
+ if (logoutUrl != null) {
+ WebApplicationService service = serviceFactory.createService(logoutUrl);
+
+ // Génère le ST factice lié au TGT factice
+ centralAuthenticationService.grantServiceTicket(fakeTgt.getId(), service, authenticationResult);
+ // Pas besoin de collecter les ST dans une liste
+ }
+ }
+
+ // Ensuite le logout général
+ return logoutManager.performLogout(
+ SingleLogoutExecutionRequest.builder().ticketGrantingTicket(fakeTgt).build()
+ );
+ } catch (Throwable e) {
+ LOGGER.error("Unable to perform general logout", e);
+ return new ArrayList<>();
+ }
+ }
+
+ @Getter
+ @RequiredArgsConstructor
+ private static class FakePrincipal implements Principal {
+
+ private final String id;
+ }
+}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/CustomerModel.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/CustomerModel.java
index 1b9f5859dd9..7a8afb53bae 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/CustomerModel.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/CustomerModel.java
@@ -38,16 +38,38 @@
*/
package fr.gouv.vitamui.cas.model;
-import lombok.Data;
-import lombok.experimental.Accessors;
-
import java.io.Serializable;
-@Data
-@Accessors(chain = true)
public class CustomerModel implements Serializable {
- String customerId;
- String code;
- String name;
+ private String customerId;
+ private String code;
+ private String name;
+
+ public String getCustomerId() {
+ return customerId;
+ }
+
+ public CustomerModel setCustomerId(String customerId) {
+ this.customerId = customerId;
+ return this;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public CustomerModel setCode(String code) {
+ this.code = code;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public CustomerModel setName(String name) {
+ this.name = name;
+ return this;
+ }
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/UserLoginModel.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/UserLoginModel.java
index 071076139a8..6d770b31ebf 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/UserLoginModel.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/model/UserLoginModel.java
@@ -40,9 +40,7 @@
package fr.gouv.vitamui.cas.model;
import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Data;
-@Data
public class UserLoginModel {
@JsonProperty("userEmail")
@@ -50,4 +48,40 @@ public class UserLoginModel {
@JsonProperty("customerId")
private String customerId;
+
+ public String getUserEmail() {
+ return userEmail;
+ }
+
+ public void setUserEmail(String userEmail) {
+ this.userEmail = userEmail;
+ }
+
+ public String getCustomerId() {
+ return customerId;
+ }
+
+ public void setCustomerId(String customerId) {
+ this.customerId = customerId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ UserLoginModel that = (UserLoginModel) o;
+ return (
+ java.util.Objects.equals(userEmail, that.userEmail) && java.util.Objects.equals(customerId, that.customerId)
+ );
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(userEmail, customerId);
+ }
+
+ @Override
+ public String toString() {
+ return "UserLoginModel{" + "userEmail='" + userEmail + '\'' + ", customerId='" + customerId + '\'' + '}';
+ }
}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/password/CustomCasWebSecurityConfigurerAdapter.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/password/CustomCasWebSecurityConfigurerAdapter.java
new file mode 100644
index 00000000000..f29f0f2e99f
--- /dev/null
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/password/CustomCasWebSecurityConfigurerAdapter.java
@@ -0,0 +1,69 @@
+/**
+ * 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.password;
+
+import lombok.val;
+import org.apereo.cas.configuration.CasConfigurationProperties;
+import org.apereo.cas.web.CasWebSecurityConfigurer;
+import org.apereo.cas.web.security.CasWebSecurityConfigurerAdapter;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
+import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+import java.util.List;
+
+/** Exclude the URL /extras from security. */
+public class CustomCasWebSecurityConfigurerAdapter extends CasWebSecurityConfigurerAdapter {
+
+ public CustomCasWebSecurityConfigurerAdapter(
+ final CasConfigurationProperties casProperties,
+ final WebEndpointProperties webEndpointProperties,
+ final ObjectProvider pathMappedEndpoints,
+ final List webSecurityConfigurers,
+ final SecurityContextRepository securityContextRepository
+ ) {
+ super(
+ casProperties,
+ webEndpointProperties,
+ pathMappedEndpoints,
+ webSecurityConfigurers,
+ securityContextRepository
+ );
+ }
+
+ @Override
+ protected List getAllowedPatternsToIgnore() {
+ val patterns = super.getAllowedPatternsToIgnore();
+ patterns.add("/extras/**");
+ return patterns;
+ }
+}
diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementService.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/password/IamPasswordManagementService.java
similarity index 83%
rename from cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementService.java
rename to cas/cas-server/src/main/java/fr/gouv/vitamui/cas/password/IamPasswordManagementService.java
index ae60692477f..179d445951f 100644
--- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/pm/IamPasswordManagementService.java
+++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/password/IamPasswordManagementService.java
@@ -34,13 +34,13 @@
* 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.pm;
+package fr.gouv.vitamui.cas.password;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
+import fr.gouv.vitamui.cas.delegation.ProvidersService;
import fr.gouv.vitamui.cas.model.UserLoginModel;
-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.domain.UserDto;
@@ -49,16 +49,15 @@
import fr.gouv.vitamui.commons.api.exception.VitamUIException;
import fr.gouv.vitamui.commons.security.client.config.password.PasswordConfiguration;
import fr.gouv.vitamui.commons.security.client.password.PasswordValidator;
-import fr.gouv.vitamui.iam.client.CasRestClient;
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
+import fr.gouv.vitamui.iam.openapiclient.CasApi;
+import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
-import lombok.val;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CentralAuthenticationService;
-import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.PreventedException;
-import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService;
import org.apereo.cas.configuration.model.support.pm.PasswordManagementProperties;
import org.apereo.cas.pm.InvalidPasswordException;
@@ -69,8 +68,6 @@
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.util.crypto.CipherExecutor;
import org.apereo.cas.web.support.WebUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
@@ -78,11 +75,9 @@
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
-import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import static fr.gouv.vitamui.commons.api.CommonConstants.SUPER_USER_ATTRIBUTE;
@@ -91,13 +86,12 @@
*/
@Getter
@Setter
+@Slf4j
public class IamPasswordManagementService extends BasePasswordManagementService {
- private static final Logger LOGGER = LoggerFactory.getLogger(IamPasswordManagementService.class);
-
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
- private final CasRestClient casRestClient;
+ private final CasApi casApi;
private final ProvidersService providersService;
@@ -113,14 +107,12 @@ public class IamPasswordManagementService extends BasePasswordManagementService
private final PasswordConfiguration passwordConfiguration;
- private static Integer maxOldPassword;
-
public IamPasswordManagementService(
final PasswordManagementProperties passwordManagementProperties,
final CipherExecutor cipherExecutor,
final String issuer,
final PasswordHistoryService passwordHistoryService,
- final CasRestClient casRestClient,
+ final CasApi casApi,
final ProvidersService providersService,
final IdentityProviderHelper identityProviderHelper,
final CentralAuthenticationService centralAuthenticationService,
@@ -130,7 +122,7 @@ public IamPasswordManagementService(
final PasswordConfiguration passwordConfiguration
) {
super(passwordManagementProperties, cipherExecutor, issuer, passwordHistoryService);
- this.casRestClient = casRestClient;
+ this.casApi = casApi;
this.providersService = providersService;
this.identityProviderHelper = identityProviderHelper;
this.centralAuthenticationService = centralAuthenticationService;
@@ -138,12 +130,11 @@ public IamPasswordManagementService(
this.ticketRegistry = ticketRegistry;
this.passwordValidator = passwordValidator;
this.passwordConfiguration = passwordConfiguration;
- this.maxOldPassword = passwordConfiguration.getMaxOldPassword();
}
protected RequestContext blockIfSubrogation() {
- val requestContext = RequestContextHolder.getRequestContext();
- val authentication = WebUtils.getAuthentication(requestContext);
+ final var requestContext = RequestContextHolder.getRequestContext();
+ final var authentication = WebUtils.getAuthentication(requestContext);
if (authentication != null) {
// login/pwd subrogation
String superUsername = (String) utils.getAttributeValue(
@@ -165,40 +156,37 @@ protected RequestContext blockIfSubrogation() {
}
@Override
- public boolean changeInternal(final Credential c, final PasswordChangeRequest bean)
- throws InvalidPasswordException {
- val requestContext = blockIfSubrogation();
- val flowScope = requestContext.getFlowScope();
+ public boolean changeInternal(final PasswordChangeRequest bean) throws InvalidPasswordException {
+ final var requestContext = blockIfSubrogation();
+ final var flowScope = requestContext.getFlowScope();
if (flowScope != null) {
flowScope.put("passwordHasBeenChanged", true);
}
- if (!passwordValidator.isEqualConfirmed(bean.getPassword(), bean.getConfirmedPassword())) {
+ String password = new String(bean.getPassword());
+ String confirmedPassword = new String(bean.getConfirmedPassword());
+
+ if (!passwordValidator.isEqualConfirmed(password, confirmedPassword)) {
throw new PasswordConfirmException();
}
- if (!passwordValidator.isValid(getProperties().getCore().getPasswordPolicyPattern(), bean.getPassword())) {
+ if (!passwordValidator.isValid(getProperties().getCore().getPasswordPolicyPattern(), password)) {
throw new PasswordNotMatchRegexException();
}
- val upc = (UsernamePasswordCredential) c;
- val username = upc.getUsername();
-
+ final var username = bean.getUsername();
LOGGER.debug("passwordConfiguration: {}", passwordConfiguration);
Assert.notNull(username, "username can not be null");
- UserLoginModel userLogin = extractUserLoginAndCustomerIdModel(flowScope, username);
- final UserDto user = casRestClient.getUserByEmailAndCustomerId(
- utils.buildContext(userLogin.getUserEmail()),
- userLogin.getUserEmail(),
- userLogin.getCustomerId(),
- Optional.empty()
- );
+ final UserLoginModel userLogin = extractUserLoginAndCustomerIdModel(flowScope, username);
+ final UserDto user = findUserByEmailAndCustomerId(userLogin.getUserEmail(), userLogin.getCustomerId());
+
if (user == null) {
LOGGER.debug("User not found with login: {}", userLogin.getUserEmail());
throw new InvalidPasswordException();
}
+
if (user.getStatus() != UserStatusEnum.ENABLED) {
LOGGER.debug("User cannot login: {} - User {}", userLogin.getUserEmail(), user.toString());
throw new InvalidPasswordException();
@@ -219,7 +207,7 @@ public boolean changeInternal(final Credential c, final PasswordChangeRequest be
if (
passwordValidator.isContainsUserOccurrences(
userLastName,
- bean.getPassword(),
+ password,
passwordConfiguration.getOccurrencesCharsNumber()
)
) {
@@ -229,7 +217,7 @@ public boolean changeInternal(final Credential c, final PasswordChangeRequest be
}
}
- val identityProvider = identityProviderHelper.findByUserIdentifierAndCustomerId(
+ final var identityProvider = identityProviderHelper.findByUserIdentifierAndCustomerId(
providersService.getProviders(),
userLogin.getUserEmail(),
userLogin.getCustomerId()
@@ -244,12 +232,7 @@ public boolean changeInternal(final Credential c, final PasswordChangeRequest be
);
try {
- casRestClient.changePassword(
- utils.buildContext(userLogin.getUserEmail()),
- userLogin.getUserEmail(),
- userLogin.getCustomerId(),
- bean.getPassword()
- );
+ casApi.changePassword(userLogin.getUserEmail(), password, userLogin.getCustomerId());
return true;
} catch (final ConflictException e) {
throw new PasswordAlreadyUsedException();
@@ -262,9 +245,11 @@ public boolean changeInternal(final Credential c, final PasswordChangeRequest be
@NotNull
private UserLoginModel extractUserLoginAndCustomerIdModel(MutableAttributeMap