Skip to content

Commit

Permalink
WIP SAML2 config and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pvannierop committed Dec 29, 2023
1 parent 8165c6a commit 38c22ff
Show file tree
Hide file tree
Showing 11 changed files with 2,153 additions and 34 deletions.
49 changes: 49 additions & 0 deletions dev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Tools for development

In this folder is some additional configuration that can be useful for local development. None of this should be deployed directly to production

# Set up keycloak for cBioPortal >v6

Requirements:
- System runs docker (including docker compose)

1. Run from the root of the repository:

```
cd dev
docker compose up -d
```

2. (Option 1) Apply SAML2 configuration to _security.properties_ in cBioPortal:

```properties
authenticate=saml
spring.security.saml2.relyingparty.registration.keycloak.assertingparty.metadata-uri=http://localhost:8084/realms/cbio/protocol/saml/descriptor
spring.security.saml2.relyingparty.registration.keycloak.assertingparty.entity-id=http://localhost:8084/realms/cbio
spring.security.saml2.relyingparty.registration.keycloak.entity-id=cbioportal
spring.security.saml2.relyingparty.registration.keycloak.signing.credentials[0].certificate-location=classpath:/dev/security/signing-cert.pem
spring.security.saml2.relyingparty.registration.keycloak.signing.credentials[0].private-key-location=classpath:/dev/security/signing-key.pem
```

3. (Option 2) Apply OIDC configuration to _security.properties_ in cBioPortal:

```properties
WIP
```

4. Set the following in _portal.properties_:

```properties
persistence.cache_type=no-cache
session.service.url=http://localhost:5000/api/sessions/my_portal/

spring.datasource.url=jdbc:mysql://localhost:3306/cbioportal?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=cbio_user
spring.datasource.password=somepassword
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
```

4. Start cBioPortal application on port 8080. The login credentials are `testuser:P@assword1`.

⚠️ Warning: Do not use this directly for production use as it takes several shortcuts to get a quick keycloak instance up.
11 changes: 5 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<frontend.groupId>com.github.cbioportal</frontend.groupId>
<frontend.version>v5.4.9</frontend.version>

<!-- THIS SHOULD BE KEPT IN SYNC TO VERSION IN CGDS.SQL -->
<!-- THIS SHOULD BE KEPT IN SYNC TO VERSION IN CGDS.SQL -->
<db.version>2.13.1</db.version>

<!-- Version properties for dependencies that should have same version. -->
Expand Down Expand Up @@ -248,11 +248,10 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>${selenium_chrome_driver.version}</version>
<scope>test</scope>
</dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
Expand Down Expand Up @@ -55,7 +56,9 @@ public CancerStudyTags getTags(String studyId) {

@Override
public List<CancerStudyTags> getTagsForMultipleStudies(List<String> studyIds) {

if (studyIds == null || studyIds.isEmpty()) {
return new ArrayList<>();
}
return studyMapper.getTagsForMultipleStudies(studyIds);
}
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,74 @@
package org.cbioportal.security.config;

import org.cbioportal.security.util.GrantedAuthorityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Configuration
@EnableWebSecurity
@ConditionalOnProperty(value = "authenticate", havingValue = "saml")
public class Saml2SecurityConfig {

@Autowired
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;

@Bean
public SecurityFilterChain samlFilterChain(HttpSecurity http) throws Exception {
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(rolesConverter());

return http.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/health", "/login", "/images/**").permitAll()
DefaultSecurityFilterChain build = http
// FIXME - csrf should be enabled
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/health", "/images/**", "/js/**").permitAll()
.anyRequest().authenticated())
.exceptionHandling(eh ->
eh.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AntPathRequestMatcher.antMatcher("/api/**")))
.exceptionHandling(eh ->
eh.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AntPathRequestMatcher.antMatcher("/api/**")
)
)
.saml2Login(saml2 -> saml2
.authenticationManager(new ProviderManager(authenticationProvider))
)
.logout(logout -> logout.logoutSuccessUrl("/login?logout_success"))
// FIXME - csrf should be enabled
.csrf(AbstractHttpConfigurer::disable)
// NOTE: I did not get the official .saml2Logout() DSL to work as
// described at https://docs.spring.io/spring-security/reference/6.1/servlet/saml2/logout.html
// Logout Service POST Binding URL: http://localhost:8080/logout/saml2/slo
.logout(logout -> logout
.logoutUrl("/saml/logout")
.logoutSuccessHandler(logoutSuccessHandler())
)
.build();
return build;
}

private Converter<OpenSaml4AuthenticationProvider.ResponseToken, Saml2Authentication> rolesConverter() {

Converter<OpenSaml4AuthenticationProvider.ResponseToken, Saml2Authentication> delegate =
Expand All @@ -63,4 +87,15 @@ private Converter<OpenSaml4AuthenticationProvider.ResponseToken, Saml2Authentica
return new Saml2Authentication(principal, authentication.getSaml2Response(), mappedAuthorities);
};
}

@Bean
public LogoutSuccessHandler logoutSuccessHandler() {
// Perform logout at the SAML2 IDP
DefaultRelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository);
OpenSaml4LogoutRequestResolver logoutRequestResolver =
new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
return new Saml2RelyingPartyInitiatedLogoutSuccessHandler(logoutRequestResolver);
}

}
2 changes: 2 additions & 0 deletions src/main/java/org/cbioportal/web/StudyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
Expand Down Expand Up @@ -194,6 +195,7 @@ public ResponseEntity<Object> getTags(
}

@PreAuthorize("hasPermission(#studyIds, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@PreFilter("hasPermission(#studyIds, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@RequestMapping(value = "/studies/tags/fetch", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Get the study tags by IDs")
Expand Down
Loading

0 comments on commit 38c22ff

Please sign in to comment.