Skip to content

Commit

Permalink
feat: implement authorization configuration ability & support for MFA…
Browse files Browse the repository at this point in the history
…, replace deprecated JwtHelper from one of two places of usage
  • Loading branch information
Aiosa committed Dec 6, 2024
1 parent 9862072 commit 8636b4a
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 92 deletions.
6 changes: 5 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,11 @@
<version>${apache_httpclient.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.41.2</version>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/org/cbioportal/persistence/SecurityRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,36 @@
import org.cbioportal.model.UserAuthorities;

/**
* Interface to use to retrieve
* portal user information.
* The resolver class implementing SecurityRepository interface
* can define how users (and their rights) are evaluated.
* Depending on the resolver type (template), it can be used in different
* contexts. For example, FullAccessResolver implements SecurityRepository<Object>
* can be used anywhere since it implements object. If you need to access specific properties
* of the user authentication context, you have to implement for example
* SecurityRepository<OidcUser> interface, but then your resolver is usable only
* with authentication type of oauth2.
*/
public interface SecurityRepository {
public interface SecurityRepository<AuthUserContext> {

/**
* 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
*/

package org.cbioportal.persistence.mybatis;

Expand All @@ -45,26 +45,37 @@
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;

/**
* Security resolver usable for any authentication scheme.
* Requires presence of user entries & roles in the local database.
* Even a successful authentication over a third-party provider will not
* be accepted if the user does not exist in the database.
*/
@Repository
public class SecurityMyBatisRepository implements SecurityRepository {
@ConditionalOnProperty(name = "security.repository.type", havingValue = "cbioportal", matchIfMissing = true)
public class SecurityMyBatisRepository implements SecurityRepository<Object> {

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());
Expand All @@ -79,10 +90,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);
}

Expand All @@ -107,10 +119,10 @@ public void addPortalUserAuthorities(UserAuthorities userAuthorities) {
*/
@Override
public Set<String> getCancerStudyGroups(Integer internalCancerStudyId) {
String groups = securityMapper.getCancerStudyGroups(internalCancerStudyId);
String groups = studyGroupMapper.getCancerStudyGroups(internalCancerStudyId);
if (groups == null) {
return Collections.emptySet();
}
return new HashSet<String>(Arrays.asList(groups.toUpperCase().split(";")));
return new HashSet<String>(Arrays.asList(groups.toUpperCase().split(";")));
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public class CancerStudyPermissionEvaluator implements PermissionEvaluator {
// PUBLIC_CANCER_STUDIES_GROUP = null;
// }
// }

public CancerStudyPermissionEvaluator(final String appName, final String doFilterGroupsByAppName, final String alwaysShowCancerStudyGroup, final CacheMapUtil cacheMapUtil ) {
this.APP_NAME = appName;
this.FILTER_GROUPS_BY_APP_NAME = doFilterGroupsByAppName;
Expand All @@ -110,7 +110,7 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO
log.debug("hasPermission(), checking permissions on targetDomainObject");
}
if (targetDomainObject == null) {
if (log.isDebugEnabled()) {
if (log.isDebugEnabled()) {
log.debug("hasPermission(), targetDomainObject is null, returning false");
}
return false;
Expand Down Expand Up @@ -165,7 +165,7 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if (log.isDebugEnabled()) {
log.debug("hasPermission(), checking permissions on targetId");
log.debug("hasPermission(), checking permissions on targetId " + targetType);
}
if (targetId == null) {
if (log.isDebugEnabled()) {
Expand Down Expand Up @@ -264,17 +264,17 @@ private boolean hasAccessToCancerStudy(Authentication authentication, CancerStud
}
// if a user has access to 'all_tcga', simply return true for tcga studies
if (grantedAuthorities.contains(ALL_TCGA_CANCER_STUDIES_ID.toUpperCase()) &&
stableStudyID.toUpperCase().endsWith("_TCGA")) {
stableStudyID.toUpperCase().endsWith("_TCGA")) {
if (log.isDebugEnabled()) {
log.debug("hasAccessToCancerStudy(), user has access to ALL_TCGA cancer studies return true");
}
return true;
}
// if a user has access to 'all_target', simply return true for target studies
if (grantedAuthorities.contains(ALL_TARGET_CANCER_STUDIES_ID.toUpperCase()) &&
(stableStudyID.toUpperCase().endsWith("_TARGET")
|| stableStudyID.equalsIgnoreCase("ALL_TARGET_PHASE1")
|| stableStudyID.equalsIgnoreCase("ALL_TARGET_PHASE2"))) {
(stableStudyID.toUpperCase().endsWith("_TARGET")
|| stableStudyID.equalsIgnoreCase("ALL_TARGET_PHASE1")
|| stableStudyID.equalsIgnoreCase("ALL_TARGET_PHASE2"))) {
if (log.isDebugEnabled()) {
log.debug("hasAccessToCancerStudy(), user has access to ALL_NCI_TARGET cancer studies return true");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,12 +30,12 @@
public class CustomOAuth2AuthorizationConfig {
Logger log = LoggerFactory.getLogger(CustomOAuth2AuthorizationConfig.class);

private final SecurityRepository securityRepository;
private final SecurityRepository<OidcUser> securityRepository;

private static final String NAME_ATTRIBUTE_KEY = "email";

@Autowired
public CustomOAuth2AuthorizationConfig(SecurityRepository securityRepository) {
public CustomOAuth2AuthorizationConfig(SecurityRepository<OidcUser> securityRepository) {
this.securityRepository = securityRepository;
}

Expand All @@ -48,9 +49,9 @@ public OAuth2UserService<OidcUserRequest, OidcUser> 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<GrantedAuthority> mappedAuthorities = authenticatedPortalUser.authorities;
Expand All @@ -59,11 +60,11 @@ public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
};
}

private AuthenticatedPortalUser loadPortalUser(String email) {
private AuthenticatedPortalUser loadPortalUser(String username, OidcUser user) {
Set<GrantedAuthority> 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()));
}
Expand Down
Loading

0 comments on commit 8636b4a

Please sign in to comment.