Unverified Commit cc883b9d authored by Le Roy Loïc's avatar Le Roy Loïc Committed by GitHub
Browse files

Merge pull request #50 from ProgrammeVitam/security_fixes

Correction de bugs en lien avec la sécurité et CAS
parents aca573fd 814d3258
......@@ -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"));
......
......@@ -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"));
......
......@@ -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>
......
......@@ -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());
......
......@@ -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";
......
......@@ -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";
......
......@@ -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}`
......@@ -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>
......
......@@ -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);
......
......@@ -134,7 +134,7 @@ public class CustomLoginWebflowConfigurer extends DefaultLoginWebflowConfigurer
createTransitionForState(handler, AccountDisabledException.class.getSimpleName(), CasWebflowConstants.VIEW_ID_ACCOUNT_DISABLED);
createTransitionForState(handler, AccountLockedException.class.getSimpleName(), CasWebflowConstants.VIEW_ID_ACCOUNT_LOCKED);
// custo:
createTransitionForState(handler, AccountPasswordMustChangeException.class.getSimpleName(), "sendInstructions");
createTransitionForState(handler, AccountPasswordMustChangeException.class.getSimpleName(), CasWebflowConstants.VIEW_ID_SEND_RESET_PASSWORD_ACCT_INFO);
createTransitionForState(handler, CredentialExpiredException.class.getSimpleName(), CasWebflowConstants.VIEW_ID_EXPIRED_PASSWORD);
createTransitionForState(handler, InvalidLoginLocationException.class.getSimpleName(), CasWebflowConstants.VIEW_ID_INVALID_WORKSTATION);
createTransitionForState(handler, InvalidLoginTimeException.class.getSimpleName(), CasWebflowConstants.VIEW_ID_INVALID_AUTHENTICATION_HOURS);
......
......@@ -81,7 +81,7 @@
<div>
<ol start="a">
<ol style="list-style: none" start="a">
<li th:each="entry,iterStat : ${logoutUrls}">
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
......
......@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta charset="UTF-8"/>
<title>${@environment.getProperty('theme.vitamui-platform-name')}</title>
<title th:text="${@environment.getProperty('theme.vitamui-platform-name')}"></title>
<meta http-equiv="refresh" th:attr="content=${'1; url=' + url}" />
<meta name="viewport" content="width=device-width, initial-scale=1">
......
......@@ -46,9 +46,6 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.Assert;
......@@ -56,6 +53,9 @@ import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import fr.gouv.vitamui.commons.api.exception.ApplicationServerException;
import fr.gouv.vitamui.commons.api.logger.VitamUILogger;
import fr.gouv.vitamui.commons.api.logger.VitamUILoggerFactory;
......@@ -150,8 +150,14 @@ public class BaseWebClientFactory implements WebClientFactory {
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
sslContextBuilder = sslContextBuilder.clientAuth(ClientAuth.NONE);
if (ks != null) {
sslContextBuilder = sslContextBuilder.keyManager(createKeyManagerFactory(ks.getType(), ks.getKeyPath(), ks.getKeyPassword().toCharArray()));
if (restClientConfig.isNoClientAuthentication()) {
LOGGER.warn("By deactivating the authentication client we deprive ourselves of two-way authentication.");
} else {
if (ks != null) {
sslContextBuilder = sslContextBuilder.keyManager(createKeyManagerFactory(ks.getType(), ks.getKeyPath(), ks.getKeyPassword().toCharArray()));
}
}
if (restClientConfig.getSslConfiguration().isHostnameVerification()) {
......
......@@ -59,6 +59,8 @@ public class RestClientConfiguration {
private boolean secure;
private boolean noClientAuthentication = false;
private SSLConfiguration sslConfiguration;
/**
......
......@@ -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>
......
......@@ -163,7 +163,7 @@ public class ApplicationService extends AbstractCrudService<ApplicationDto> {
}
public String getBase64File(String fileName, String basePath) {
final Path assetFile = Paths.get(basePath, fileName).normalize();
final Path assetFile = Paths.get(basePath, Paths.get(fileName).getFileName().toString());
String base64Asset = null;
try {
base64Asset = DatatypeConverter.printBase64Binary(Files.readAllBytes(assetFile));
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment