diff --git a/src/main/java/org/cbioportal/persistence/SecurityRepository.java b/src/main/java/org/cbioportal/persistence/SecurityRepository.java index 68ff9f1a3a5..bf60efd176a 100644 --- a/src/main/java/org/cbioportal/persistence/SecurityRepository.java +++ b/src/main/java/org/cbioportal/persistence/SecurityRepository.java @@ -42,25 +42,27 @@ * Interface to use to retrieve * portal user information. */ -public interface SecurityRepository { +public interface SecurityRepository { /** * Given a user id, returns a user instance. * If username does not exist in db, returns null. * * @param username String + * @param user object that has necessary user information * @return User */ - User getPortalUser(String username); + User getPortalUser(String username, AuthUserContext user); /** * Given a user id, returns a UserAuthorities instance. * If username does not exist in db, returns null. * * @param username String + * @param user object that has necessary user information * @return UserAuthorities */ - UserAuthorities getPortalUserAuthorities(String username); + UserAuthorities getPortalUserAuthorities(String username, AuthUserContext user); void addPortalUser(User user); void addPortalUserAuthorities(UserAuthorities userAuthorities); diff --git a/src/main/java/org/cbioportal/persistence/mybatis/SecurityMapper.java b/src/main/java/org/cbioportal/persistence/mybatis/SecurityMapper.java index 3f47af78354..dcde6937ccc 100644 --- a/src/main/java/org/cbioportal/persistence/mybatis/SecurityMapper.java +++ b/src/main/java/org/cbioportal/persistence/mybatis/SecurityMapper.java @@ -64,13 +64,4 @@ public interface SecurityMapper { void addPortalUser(User user); void addPortalUserAuthority(@Param("email") String email, @Param("authority") String authority); - - /** - * Given an internal cancer study id, returns groups string. - * Returns null if cancer study does not exist. - * - * @param internalCancerStudyId Integer - * @return String groups - */ - String getCancerStudyGroups(Integer internalCancerStudyId); } diff --git a/src/main/java/org/cbioportal/persistence/mybatis/SecurityMyBatisRepository.java b/src/main/java/org/cbioportal/persistence/mybatis/SecurityMyBatisRepository.java index 3172948d2a5..044f2088dd8 100644 --- a/src/main/java/org/cbioportal/persistence/mybatis/SecurityMyBatisRepository.java +++ b/src/main/java/org/cbioportal/persistence/mybatis/SecurityMyBatisRepository.java @@ -45,26 +45,31 @@ import org.cbioportal.model.UserAuthorities; import org.cbioportal.persistence.SecurityRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository -public class SecurityMyBatisRepository implements SecurityRepository { +@ConditionalOnProperty(name = "security.repository.type", havingValue = "cbioportal", matchIfMissing = true) +public class SecurityMyBatisRepository implements SecurityRepository { private static final Logger log = LoggerFactory.getLogger(SecurityMyBatisRepository.class); @Autowired private SecurityMapper securityMapper; - + @Autowired + private StudyGroupMapper studyGroupMapper; + /** * Given a user id, returns a user instance. * If username does not exist in db, returns null. * * @param username String + * @param _unused * @return User */ @Override - public User getPortalUser(String username) { + public User getPortalUser(String username, Object _unused) { User user = securityMapper.getPortalUser(username); if (user != null) { log.debug("User " + username + " was found in the users table, email is " + user.getEmail()); @@ -79,10 +84,11 @@ public User getPortalUser(String username) { * If username does not exist in db, returns null. * * @param username String + * @param _unused * @return UserAuthorities */ @Override - public UserAuthorities getPortalUserAuthorities(String username) { + public UserAuthorities getPortalUserAuthorities(String username, Object _unused) { return securityMapper.getPortalUserAuthorities(username); } @@ -107,7 +113,7 @@ public void addPortalUserAuthorities(UserAuthorities userAuthorities) { */ @Override public Set getCancerStudyGroups(Integer internalCancerStudyId) { - String groups = securityMapper.getCancerStudyGroups(internalCancerStudyId); + String groups = studyGroupMapper.getCancerStudyGroups(internalCancerStudyId); if (groups == null) { return Collections.emptySet(); } diff --git a/src/main/java/org/cbioportal/persistence/mybatis/StudyGroupMapper.java b/src/main/java/org/cbioportal/persistence/mybatis/StudyGroupMapper.java new file mode 100644 index 00000000000..58532fed986 --- /dev/null +++ b/src/main/java/org/cbioportal/persistence/mybatis/StudyGroupMapper.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015 Memorial Sloan-Kettering Cancer Center. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS + * FOR A PARTICULAR PURPOSE. The software and documentation provided hereunder + * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no + * obligations to provide maintenance, support, updates, enhancements or + * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be + * liable to any party for direct, indirect, special, incidental or + * consequential damages, including lost profits, arising out of the use of this + * software and its documentation, even if Memorial Sloan-Kettering Cancer + * Center has been advised of the possibility of such damage. + */ + +/* + * This file is part of cBioPortal. + * + * cBioPortal is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +*/ + +package org.cbioportal.persistence.mybatis; + +// imports +import org.apache.ibatis.annotations.Param; + +/** + * Interface to use to retrieve + * portal user information. + */ +public interface StudyGroupMapper { + /** + * Given an internal cancer study id, returns groups string. + * Returns null if cancer study does not exist. + * + * @param internalCancerStudyId Integer + * @return String groups + */ + String getCancerStudyGroups(Integer internalCancerStudyId); +} diff --git a/src/main/java/org/cbioportal/security/config/CustomOAuth2AuthorizationConfig.java b/src/main/java/org/cbioportal/security/config/CustomOAuth2AuthorizationConfig.java index 7929fdce39f..cd3b6ea371c 100644 --- a/src/main/java/org/cbioportal/security/config/CustomOAuth2AuthorizationConfig.java +++ b/src/main/java/org/cbioportal/security/config/CustomOAuth2AuthorizationConfig.java @@ -18,6 +18,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; import java.util.HashSet; import java.util.Objects; @@ -29,12 +30,12 @@ public class CustomOAuth2AuthorizationConfig { Logger log = LoggerFactory.getLogger(CustomOAuth2AuthorizationConfig.class); - private final SecurityRepository securityRepository; + private final SecurityRepository securityRepository; private static final String NAME_ATTRIBUTE_KEY = "email"; @Autowired - public CustomOAuth2AuthorizationConfig(SecurityRepository securityRepository) { + public CustomOAuth2AuthorizationConfig(SecurityRepository securityRepository) { this.securityRepository = securityRepository; } @@ -48,9 +49,9 @@ public OAuth2UserService oidcUserService() { // Delegate to the default implementation for loading a user OidcUser oidcUser = delegate.loadUser(userRequest); - var authenticatedPortalUser = loadPortalUser(oidcUser.getEmail()); + var authenticatedPortalUser = loadPortalUser(oidcUser.getEmail(), oidcUser); if (Objects.isNull(authenticatedPortalUser.cbioUser) || !authenticatedPortalUser.cbioUser.isEnabled()) { - log.debug("User: {} either not in db or not authorized", oidcUser.getEmail()); + log.error("User: {} either not in db or not authorized", oidcUser.getEmail()); throw new OAuth2AuthenticationException("user not authorized"); } Set mappedAuthorities = authenticatedPortalUser.authorities; @@ -59,11 +60,11 @@ public OAuth2UserService oidcUserService() { }; } - private AuthenticatedPortalUser loadPortalUser(String email) { + private AuthenticatedPortalUser loadPortalUser(String username, OidcUser user) { Set mappedAuthorities = new HashSet<>(); - User cbioUser = securityRepository.getPortalUser(email); + User cbioUser = securityRepository.getPortalUser(username, user); if (!Objects.isNull(cbioUser)) { - UserAuthorities authorities = securityRepository.getPortalUserAuthorities(email); + UserAuthorities authorities = securityRepository.getPortalUserAuthorities(username, user); if (!Objects.isNull(authorities)) { mappedAuthorities.addAll(AuthorityUtils.createAuthorityList(authorities.getAuthorities())); } diff --git a/src/main/java/org/cbioportal/security/config/access/FullAccessResolver.java b/src/main/java/org/cbioportal/security/config/access/FullAccessResolver.java new file mode 100644 index 00000000000..39330ef0be3 --- /dev/null +++ b/src/main/java/org/cbioportal/security/config/access/FullAccessResolver.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 Memorial Sloan-Kettering Cancer Center. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS + * FOR A PARTICULAR PURPOSE. The software and documentation provided hereunder + * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no + * obligations to provide maintenance, support, updates, enhancements or + * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be + * liable to any party for direct, indirect, special, incidental or + * consequential damages, including lost profits, arising out of the use of this + * software and its documentation, even if Memorial Sloan-Kettering Cancer + * Center has been advised of the possibility of such damage. + */ + +/* + * This file is part of cBioPortal. + * + * cBioPortal is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +*/ + +package org.cbioportal.security.config.access; + +// imports +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.cbioportal.model.User; +import org.cbioportal.model.UserAuthorities; +import org.cbioportal.persistence.SecurityRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import org.springframework.stereotype.Service; + +@Service +@ConditionalOnProperty(name = "security.repository.type", havingValue = "disabled") +public class FullAccessResolver implements SecurityRepository { + + private static final Logger log = LoggerFactory.getLogger(FullAccessResolver.class); + + /** + * Always returns a valid user. + * + * @param username String + * @param user Object + * @return User + */ + @Override + public User getPortalUser(String username, Object user) { + return new User(username, username, true); + } + + /** + * Given a user id, returns a UserAuthorities instance. + * If username does not exist in db, returns null. + * + * @param username String + * @param user Object + * @return UserAuthorities + */ + @Override + public UserAuthorities getPortalUserAuthorities(String username, Object user) { + return new UserAuthorities(); + } + + @Override + public void addPortalUser(User user) { + //no-op + } + + @Override + public void addPortalUserAuthorities(UserAuthorities userAuthorities) { + //no-op + } + + /** + * Given an internal cancer study id, returns a set of upper case cancer study group strings. + * Returns empty set if cancer study does not exist or there are no groups. + * + * @param internalCancerStudyId Integer + * @return Set cancer study group strings in upper case + */ + @Override + public Set getCancerStudyGroups(Integer internalCancerStudyId) { + return Collections.emptySet(); + } +} diff --git a/src/main/java/org/cbioportal/security/config/access/LsaaiOauth2DrivenResolver.java b/src/main/java/org/cbioportal/security/config/access/LsaaiOauth2DrivenResolver.java new file mode 100644 index 00000000000..879629e7335 --- /dev/null +++ b/src/main/java/org/cbioportal/security/config/access/LsaaiOauth2DrivenResolver.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2015 Memorial Sloan-Kettering Cancer Center. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS + * FOR A PARTICULAR PURPOSE. The software and documentation provided hereunder + * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no + * obligations to provide maintenance, support, updates, enhancements or + * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be + * liable to any party for direct, indirect, special, incidental or + * consequential damages, including lost profits, arising out of the use of this + * software and its documentation, even if Memorial Sloan-Kettering Cancer + * Center has been advised of the possibility of such damage. + */ + +/* + * This file is part of cBioPortal. + * + * cBioPortal is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.cbioportal.security.config.access; + +// imports +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.net.URI; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.cbioportal.model.User; +import org.cbioportal.model.UserAuthorities; +import org.cbioportal.persistence.SecurityRepository; +import org.cbioportal.persistence.mybatis.StudyGroupMapper; + +import org.springframework.stereotype.Service; + +import org.springframework.web.client.RestTemplate; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; + +@Service +@ConditionalOnProperty(name = "security.repository.type", havingValue = "lsaai") +//todo also condition that auth is oauth2 +public class LsaaiOauth2DrivenResolver implements SecurityRepository { + + private static final Logger log = LoggerFactory.getLogger(LsaaiOauth2DrivenResolver.class); + + @Autowired + private StudyGroupMapper studyGroupMapper; + + @Value("${security.repository.lsaai.userinfo}") + private String apiUrl; + + private UserAuthorities authorities = null; + + /** + * Always returns a valid user. + * + * @param username String + * @return User + */ + @Override + public User getPortalUser(String username, OidcUser user) { + if (authorities == null) { + parseUserInfo(username, user.getUserInfo()); + } + return new User(username, username, true); + } + + /** + * Given a user id, returns a UserAuthorities instance. + * If username does not exist in db, returns null. + * + * @param username String + * @return UserAuthorities + */ + @Override + public UserAuthorities getPortalUserAuthorities(String username, OidcUser user) { + return authorities; + } + + @Override + public void addPortalUser(User user) { + //no-op + } + + @Override + public void addPortalUserAuthorities(UserAuthorities userAuthorities) { + //no-op + } + + /** + * Given an internal cancer study id, returns a set of upper case cancer study group strings. + * Returns empty set if cancer study does not exist or there are no groups. + * + * @param internalCancerStudyId Integer + * @return Set cancer study group strings in upper case + */ + @Override + public Set getCancerStudyGroups(Integer internalCancerStudyId) { + String groups = studyGroupMapper.getCancerStudyGroups(internalCancerStudyId); + if (groups == null) { + return Collections.emptySet(); + } + return new HashSet(Arrays.asList(groups.toUpperCase().split(";"))); + } + + private void parseUserInfo(String username, OidcUserInfo info) { + if (info == null) { + log.warn("LSAAI is missing user info object " + username); + return; + } + + var claims = info.getClaims(); + String[] entitlements; + Object entitlementObj = claims.get("eduperson_entitlement"); + + if (entitlementObj instanceof String[]) { + entitlements = (String[]) entitlementObj; // Cast to String array + } else if (entitlementObj instanceof List) { + List entitlementList = (List) entitlementObj; // Cast to List + entitlements = entitlementList.toArray(new String[0]); // Convert List to String array + } else { + log.warn("LSAAI is missing eduperson_entitlement information: user access rights cannot be validated for user " + username); + return; + } + + // We get institution - project couples: + List result = new ArrayList<>(); + for (String rule : entitlements) { + var roles = extractNLastFromArgRule(rule, 2); + if (roles != null) { + result.add(roles[0] + ":" + roles[1]); + } + } + authorities = new UserAuthorities(username, result); + } + + private String[] extractNLastFromArgRule(String input, int size) { + int index = input.indexOf(":res:"); + if (index >= 0) { + // Keep only the part after ':res:' and skip it + String attributes = input.substring(index + 5); + String cleanedInput = attributes.contains("#") ? attributes.split("#")[0] : attributes; + String[] parts = cleanedInput.split(":"); + + if (parts.length >= size) { + return Arrays.copyOfRange(parts, parts.length - size, parts.length); + } + log.debug("Parsed ARG rule does not contain enough elements! " + input); + } + return null; + } +} diff --git a/src/main/java/org/cbioportal/security/token/uuid/UuidTokenAuthenticationProvider.java b/src/main/java/org/cbioportal/security/token/uuid/UuidTokenAuthenticationProvider.java index 85268a3df22..191c39fd651 100644 --- a/src/main/java/org/cbioportal/security/token/uuid/UuidTokenAuthenticationProvider.java +++ b/src/main/java/org/cbioportal/security/token/uuid/UuidTokenAuthenticationProvider.java @@ -28,7 +28,9 @@ public UuidTokenAuthenticationProvider(final SecurityRepository securityReposito public Authentication authenticate(Authentication authentication) throws AuthenticationException { String user = (String) authentication.getPrincipal(); log.debug("Attempt to grab user Authorities for user: {}", user); - UserAuthorities authorities = securityRepository.getPortalUserAuthorities(user); + + // TODO: consider supplying some secret here (what are the usecases?), for now we submit null + UserAuthorities authorities = securityRepository.getPortalUserAuthorities(user, null); Set mappedAuthorities = new HashSet<>(); if (!Objects.isNull(authorities)) { mappedAuthorities.addAll(AuthorityUtils.createAuthorityList(authorities.getAuthorities())); diff --git a/src/main/resources/org/cbioportal/persistence/mybatis/SecurityMapper.xml b/src/main/resources/org/cbioportal/persistence/mybatis/SecurityMapper.xml index 77eb4a73004..239caa347c3 100644 --- a/src/main/resources/org/cbioportal/persistence/mybatis/SecurityMapper.xml +++ b/src/main/resources/org/cbioportal/persistence/mybatis/SecurityMapper.xml @@ -38,11 +38,4 @@ INSERT INTO authorities (email, authority) VALUES (LOWER(#{email}), #{authority}) - - diff --git a/src/main/resources/org/cbioportal/persistence/mybatis/StudyGroupsMapper.xml b/src/main/resources/org/cbioportal/persistence/mybatis/StudyGroupsMapper.xml new file mode 100644 index 00000000000..a4875d271af --- /dev/null +++ b/src/main/resources/org/cbioportal/persistence/mybatis/StudyGroupsMapper.xml @@ -0,0 +1,13 @@ + + + + + + + +