From 710c9371dc7da9213d6b22cecfabd91d8ca53522 Mon Sep 17 00:00:00 2001 From: Makhtar DIAGNE <makhtar.diagne@teamdlab.com> Date: Mon, 31 Aug 2020 00:55:37 +0200 Subject: [PATCH] [TECH] Fix oauth authentication : use same token generation as CAS --- .../client/CasExternalRestClientTest.java | 2 +- .../client/CasInternalRestClientTest.java | 2 +- api/api-iam/iam-internal/pom.xml | 46 +++++++++++++++ .../cas/service/CasInternalService.java | 8 ++- .../service/UserInternalServiceIntegTest.java | 2 +- .../user/service/UserInternalServiceTest.java | 2 +- cas/cas-server/README.md | 4 +- cas/cas-server/pom.xml | 10 ++++ .../fr/gouv/vitamui/cas/config/AppConfig.java | 59 ++++++++++++++++++- pom.xml | 24 ++++++++ 10 files changed, 149 insertions(+), 10 deletions(-) diff --git a/api/api-iam/iam-external-client/src/test/java/fr/gouv/vitamui/iam/external/client/CasExternalRestClientTest.java b/api/api-iam/iam-external-client/src/test/java/fr/gouv/vitamui/iam/external/client/CasExternalRestClientTest.java index de997f8f..da5161cc 100644 --- a/api/api-iam/iam-external-client/src/test/java/fr/gouv/vitamui/iam/external/client/CasExternalRestClientTest.java +++ b/api/api-iam/iam-external-client/src/test/java/fr/gouv/vitamui/iam/external/client/CasExternalRestClientTest.java @@ -38,7 +38,7 @@ public class CasExternalRestClientTest extends AbstractServerIdentityBuilder { .thenReturn(new ResponseEntity<>(HttpStatus.OK)); final String superUser = "julien@vitamui.com"; - final String authToken = "TOKyyy"; + final String authToken = "TOK-1-F8lEhVif0FWjgDF32ov73TtKhE6mflRu"; client.logout(header, authToken, superUser); final String path = RestApi.CAS_LOGOUT_PATH + "?authToken=" + authToken + "&superUser=" + superUser; assertThat(argumentCaptor.getValue().toString()).endsWith(path.replaceAll(CommonConstants.EMAIL_SEPARATOR, "%40")); diff --git a/api/api-iam/iam-internal-client/src/test/java/fr/gouv/vitamui/iam/internal/client/CasInternalRestClientTest.java b/api/api-iam/iam-internal-client/src/test/java/fr/gouv/vitamui/iam/internal/client/CasInternalRestClientTest.java index 1a12242e..c31be6cd 100644 --- a/api/api-iam/iam-internal-client/src/test/java/fr/gouv/vitamui/iam/internal/client/CasInternalRestClientTest.java +++ b/api/api-iam/iam-internal-client/src/test/java/fr/gouv/vitamui/iam/internal/client/CasInternalRestClientTest.java @@ -39,7 +39,7 @@ public class CasInternalRestClientTest extends AbstractServerIdentityBuilder { .thenReturn(new ResponseEntity<>(HttpStatus.OK)); final String superUser = "julien@vitamui.com"; - final String authToken = "TOKxxx"; + final String authToken = "TOK-1-F8lEhVif0FWjgDF32ov73TtKhE6mflRu"; client.logout(header, authToken, superUser); final String path = RestApi.CAS_LOGOUT_PATH + "?authToken=" + authToken + "&superUser=" + superUser; assertThat(argumentCaptor.getValue().toString()).endsWith(path.replaceAll(CommonConstants.EMAIL_SEPARATOR, "%40")); diff --git a/api/api-iam/iam-internal/pom.xml b/api/api-iam/iam-internal/pom.xml index 707d3f0f..caaf5792 100644 --- a/api/api-iam/iam-internal/pom.xml +++ b/api/api-iam/iam-internal/pom.xml @@ -168,6 +168,52 @@ <artifactId>common-private</artifactId> </dependency> + <!-- Apereo CAS Server Core Api Ticket --> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-core-api-ticket</artifactId> + <exclusions> + <exclusion> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-context</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-commons</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator</artifactId> + </exclusion> + <exclusion> + <groupId>com.zaxxer</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-core-tickets-api</artifactId> + <exclusions> + <exclusion> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-context</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-commons</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator</artifactId> + </exclusion> + <exclusion> + <groupId>com.zaxxer</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- Documentation --> <dependency> <groupId>io.springfox</groupId> diff --git a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java index e650a4b5..93c82105 100644 --- a/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java +++ b/api/api-iam/iam-internal/src/main/java/fr/gouv/vitamui/iam/internal/server/cas/service/CasInternalService.java @@ -49,6 +49,8 @@ import javax.validation.constraints.NotNull; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; +import org.apereo.cas.util.DefaultUniqueTicketIdGenerator; +import org.apereo.cas.ticket.UniqueTicketIdGenerator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.mongodb.core.MongoTemplate; @@ -162,6 +164,8 @@ public class CasInternalService { @SuppressWarnings("unused") private static final VitamUILogger LOGGER = VitamUILoggerFactory.getInstance(CasInternalService.class); + private static final UniqueTicketIdGenerator TICKET_GENERATOR = new DefaultUniqueTicketIdGenerator(); + @Transactional public void updatePassword(final String email, final String rawPassword) { final User user = checkUserInformations(email); @@ -272,7 +276,7 @@ public class CasInternalService { private void createEventsSubrogation(final UserDto surrogate, final boolean isSubrogation) { if (isSubrogation) { final Subrogation subro = subrogationRepository.findOneBySurrogate(surrogate.getEmail()); - EventType type; + final EventType type; if (surrogate.getType().equals(UserTypeEnum.GENERIC)) { type = EventType.EXT_VITAMUI_START_SURROGATE_GENERIC; } @@ -310,7 +314,7 @@ public class CasInternalService { } final Date nowPlusXMinutes = DateUtils.addMinutes(new Date(), ttlInMinutes); token.setUpdatedDate(nowPlusXMinutes); - token.setId(TOKEN_PREFIX + tokenRepository.generateSuperId().toUpperCase()); + token.setId(TICKET_GENERATOR.getNewTicketId(TOKEN_PREFIX)); token.setSurrogation(isSubrogation); tokenRepository.save(token); user.setLastConnection(OffsetDateTime.now()); diff --git a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceIntegTest.java b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceIntegTest.java index ca6223e8..28723d6e 100644 --- a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceIntegTest.java +++ b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceIntegTest.java @@ -85,7 +85,7 @@ import fr.gouv.vitamui.iam.internal.server.utils.IamServerUtilsTest; TokenRepository.class }, repositoryBaseClass = VitamUIRepositoryImpl.class) public final class UserInternalServiceIntegTest extends AbstractLogbookIntegrationTest { - private static final String TOKEN_VALUE = "TOK1234567890"; + private static final String TOKEN_VALUE = "TOK-1-F8lEhVif0FWjgDF32ov73TtKhE6mflRu"; private static final String USER_ID = "userId"; diff --git a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceTest.java b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceTest.java index 0c148337..60f0d839 100644 --- a/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceTest.java +++ b/api/api-iam/iam-internal/src/test/java/fr/gouv/vitamui/iam/internal/server/user/service/UserInternalServiceTest.java @@ -67,7 +67,7 @@ import fr.gouv.vitamui.iam.security.service.InternalSecurityService; */ public final class UserInternalServiceTest { - private static final String TOKEN_VALUE = "TOK1234567890"; + private static final String TOKEN_VALUE = "TOK-1-F8lEhVif0FWjgDF32ov73TtKhE6mflRu"; private static final String USER_ID = "userId"; diff --git a/cas/cas-server/README.md b/cas/cas-server/README.md index 9713c7f8..2f0dacfa 100644 --- a/cas/cas-server/README.md +++ b/cas/cas-server/README.md @@ -175,8 +175,8 @@ The call must be a POST request: The result contains the auth token in a plain response: -`access_token=TOK5CE669E223C9741AA6DCD46xxx02C8F6FB9DC98DC25B779&expires_in=28800` +`access_token=TOK-1-F8lEhVif0FWjgDF32ov73TtKhE6mflRu&expires_in=28800` or in a JSON response: -`{"access_token":"TOK5CE669E223C9741AA6DCD46xxx02C8F6FB9DC98DC25B779","token_type":"bearer","expires_in":28800}` +`{"access_token":"TOK-1-F8lEhVif0FWjgDF32ov73TtKhE6mflRu","token_type":"bearer","expires_in":28800}` diff --git a/cas/cas-server/pom.xml b/cas/cas-server/pom.xml index a779a938..e04775d2 100644 --- a/cas/cas-server/pom.xml +++ b/cas/cas-server/pom.xml @@ -111,6 +111,11 @@ <artifactId>cas-server-support-pac4j-core</artifactId> <version>${cas.version}</version> </dependency> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-support-pac4j-api</artifactId> + <version>${cas.version}</version> + </dependency> <dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-support-pac4j-core-clients</artifactId> @@ -131,6 +136,11 @@ <artifactId>pac4j-core</artifactId> <version>${pac4j.version}</version> </dependency> + <dependency> + <groupId>org.pac4j</groupId> + <artifactId>spring-webmvc-pac4j</artifactId> + <version>${pac4j.version}</version> + </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-saml-opensamlv3</artifactId> 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 86b862f3..0ea85b96 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 @@ -39,9 +39,17 @@ package fr.gouv.vitamui.cas.config; import fr.gouv.vitamui.cas.authentication.*; import fr.gouv.vitamui.cas.pm.IamPasswordManagementService; import lombok.SneakyThrows; +import lombok.val; +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Collectors; + import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.audit.AuditableExecution; -import org.apereo.cas.authentication.*; +import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer; +import org.apereo.cas.authentication.AuthenticationHandler; +import org.apereo.cas.authentication.AuthenticationMetaDataPopulator; +import org.apereo.cas.authentication.AuthenticationPostProcessor; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.authentication.principal.PrincipalResolver; import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService; @@ -49,13 +57,27 @@ import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.pm.PasswordHistoryService; import org.apereo.cas.pm.PasswordManagementService; import org.apereo.cas.services.ServicesManager; -import org.apereo.cas.ticket.*; +import org.apereo.cas.support.oauth.authenticator.Authenticators; +import org.apereo.cas.support.oauth.web.OAuth20HandlerInterceptorAdapter; +import org.apereo.cas.support.oauth.web.response.accesstoken.ext.AccessTokenGrantRequestExtractor; +import org.apereo.cas.ticket.BaseTicketCatalogConfigurer; +import org.apereo.cas.ticket.ExpirationPolicyBuilder; +import org.apereo.cas.ticket.TicketCatalog; +import org.apereo.cas.ticket.TicketCatalogConfigurer; +import org.apereo.cas.ticket.TicketDefinition; +import org.apereo.cas.ticket.TicketGrantingTicketFactory; +import org.apereo.cas.ticket.UniqueTicketIdGenerator; 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.token.JwtBuilder; import org.apereo.cas.util.crypto.CipherExecutor; +import org.pac4j.core.client.Client; +import org.pac4j.core.client.DirectClient; +import org.pac4j.core.config.Config; import org.pac4j.core.context.session.SessionStore; +import org.pac4j.core.http.adapter.JEEHttpActionAdapter; +import org.pac4j.springframework.web.SecurityInterceptor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -83,6 +105,7 @@ import fr.gouv.vitamui.iam.external.client.CasExternalRestClient; import fr.gouv.vitamui.iam.external.client.IamExternalRestClientFactory; import fr.gouv.vitamui.iam.external.client.IdentityProviderExternalRestClient; import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.web.servlet.HandlerInterceptor; /** * Configure all beans to customize the CAS server. @@ -196,6 +219,38 @@ public class AppConfig extends BaseTicketCatalogConfigurer { @Value("${vitamui.cas.identity}") private String casIdentity; + @Autowired + @Qualifier("oauthSecConfig") + private ObjectProvider<Config> oauthSecConfig; + + @Autowired + @Qualifier("accessTokenGrantRequestExtractors") + private Collection<AccessTokenGrantRequestExtractor> accessTokenGrantRequestExtractors; + + @Bean + public SecurityInterceptor requiresAuthenticationAuthorizeInterceptor() { + val interceptor = new SecurityInterceptor(oauthSecConfig.getObject(), Authenticators.CAS_OAUTH_CLIENT, JEEHttpActionAdapter.INSTANCE); + interceptor.setAuthorizers("none"); + return interceptor; + } + + @Bean + public SecurityInterceptor requiresAuthenticationAccessTokenInterceptor() { + val secConfig = oauthSecConfig.getObject(); + val clients = + Objects.requireNonNull(secConfig).getClients().findAllClients().stream().filter(client -> client instanceof DirectClient).map(Client::getName) + .collect(Collectors.joining(",")); + val interceptor = new SecurityInterceptor(oauthSecConfig.getObject(), clients, JEEHttpActionAdapter.INSTANCE); + interceptor.setAuthorizers("none"); + return interceptor; + } + + @Bean + public HandlerInterceptor oauthHandlerInterceptorAdapter() { + return new OAuth20HandlerInterceptorAdapter(requiresAuthenticationAccessTokenInterceptor(), requiresAuthenticationAuthorizeInterceptor(), + accessTokenGrantRequestExtractors); + } + @Bean public UserAuthenticationHandler userAuthenticationHandler() { return new UserAuthenticationHandler(servicesManager, principalFactory, casRestClient(), utils(), ipHeaderName); diff --git a/pom.xml b/pom.xml index 57943425..eef30fd6 100644 --- a/pom.xml +++ b/pom.xml @@ -468,6 +468,30 @@ <scope>test</scope> </dependency> + <!-- Apereo CAS Server Core Api Ticket --> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-core-api-ticket</artifactId> + <version>${cas.version}</version> + <exclusions> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.apereo.cas</groupId> + <artifactId>cas-server-core-tickets-api</artifactId> + <version>${cas.version}</version> + <exclusions> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- Web --> <dependency> <groupId>javax.servlet</groupId> -- GitLab