diff --git a/conf/WEB-INF/searchBundle.xml b/conf/WEB-INF/searchBundle.xml index a8636ffd96..66f4f079d3 100644 --- a/conf/WEB-INF/searchBundle.xml +++ b/conf/WEB-INF/searchBundle.xml @@ -10,7 +10,7 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - SELECT DISTINCT project.project_id, project_status_lu.project_status_id, project_status_lu.name, project_category_lu.project_category_id, project_category_lu.name, project_type_lu.project_type_id, project_type_lu.name, project.create_user, project.create_date, project.modify_user, project.modify_date FROM project INNER JOIN project_category_lu ON project.project_category_id = project_category_lu.project_category_id INNER JOIN project_status_lu ON project.project_status_id = project_status_lu.project_status_id INNER JOIN project_type_lu ON project_category_lu.project_type_id = project_type_lu.project_type_id INNER JOIN project_info ON project.project_id = project_info.project_id INNER JOIN project_info_type_lu ON project_info.project_info_type_id = project_info_type_lu.project_info_type_id LEFT OUTER JOIN tc_direct_project ON project.tc_direct_project_id = tc_direct_project.project_id WHERE + SELECT DISTINCT project.project_id, project_status_lu.project_status_id, project_status_lu.name as project_status_name, project_category_lu.project_category_id, project_category_lu.name as project_category_name, project_type_lu.project_type_id, project_type_lu.name as project_type_name, project.create_user, project.create_date, project.modify_user, project.modify_date FROM project INNER JOIN project_category_lu ON project.project_category_id = project_category_lu.project_category_id INNER JOIN project_status_lu ON project.project_status_id = project_status_lu.project_status_id INNER JOIN project_type_lu ON project_category_lu.project_type_id = project_type_lu.project_type_id INNER JOIN project_info ON project.project_id = project_info.project_id INNER JOIN project_info_type_lu ON project_info.project_info_type_id = project_info_type_lu.project_info_type_id LEFT OUTER JOIN tc_direct_project ON project.tc_direct_project_id = tc_direct_project.project_id WHERE diff --git a/src/main/java/com/cronos/onlinereview/config/TogglzConfiguration.java b/src/main/java/com/cronos/onlinereview/config/TogglzConfiguration.java index 9f5ba6b4cf..5e4e8e4edc 100644 --- a/src/main/java/com/cronos/onlinereview/config/TogglzConfiguration.java +++ b/src/main/java/com/cronos/onlinereview/config/TogglzConfiguration.java @@ -1,8 +1,9 @@ package com.cronos.onlinereview.config; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; import com.cronos.onlinereview.util.AuthorizationHelper; -import com.topcoder.onlinereview.component.security.RolePrincipal; -import com.topcoder.onlinereview.component.security.login.LoginBean; +import com.cronos.onlinereview.util.ConfigHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -14,16 +15,18 @@ import org.togglz.core.user.UserProvider; import org.togglz.servlet.util.HttpServletRequestHolder; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.util.List; -import java.util.Set; public class TogglzConfiguration implements TogglzConfig { private static final Logger logger = LoggerFactory.getLogger(TogglzConfiguration.class); @Value("#{'${togglz.roles}'.split(',')}") private List roles; + @Value("${togglz.role_key}") + private String roleKey; public Class getFeatureClass() { return TogglzFeatures.class; @@ -36,10 +39,25 @@ public StateRepository getStateRepository() { public UserProvider getUserProvider() { return () -> { HttpServletRequest request = HttpServletRequestHolder.get(); - Set roleSet = LoginBean.getUserRoles(AuthorizationHelper.getLoggedInUserId(request)); - for (RolePrincipal role : roleSet) { - if (roles.contains(role.getName())) { - return new SimpleFeatureUser("admin", true); + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie c : cookies) { + if (c.getName().equals(ConfigHelper.getV2jwtCookieName())) { + DecodedJWT jwt; + try { + jwt = AuthorizationHelper.validateJWTToken(c.getValue()); + } catch (Exception e) { + return new SimpleFeatureUser("user", false); + } + Claim claim = jwt.getClaim(roleKey); + if (claim != null) { + for (String role : claim.asArray(String.class)) { + if (roles.contains(role)) { + return new SimpleFeatureUser("admin", true); + } + } + } + } } } return new SimpleFeatureUser("user", false); diff --git a/src/main/java/com/cronos/onlinereview/util/AuthorizationHelper.java b/src/main/java/com/cronos/onlinereview/util/AuthorizationHelper.java index 829a05073d..bea92da1b0 100644 --- a/src/main/java/com/cronos/onlinereview/util/AuthorizationHelper.java +++ b/src/main/java/com/cronos/onlinereview/util/AuthorizationHelper.java @@ -3,11 +3,25 @@ */ package com.cronos.onlinereview.util; +import com.auth0.jwk.GuavaCachedJwkProvider; +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.Verification; import com.cronos.onlinereview.Constants; import com.topcoder.onlinereview.component.dataaccess.ProjectDataAccess; import com.topcoder.onlinereview.component.exception.BaseException; import com.topcoder.onlinereview.component.external.ExternalUser; import com.topcoder.onlinereview.component.external.UserRetrieval; +import com.topcoder.onlinereview.component.jwt.InvalidTokenException; +import com.topcoder.onlinereview.component.jwt.JWTException; import com.topcoder.onlinereview.component.project.management.Project; import com.topcoder.onlinereview.component.project.management.ProjectManager; import com.topcoder.onlinereview.component.resource.Resource; @@ -22,7 +36,10 @@ import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.http.HttpServletRequest; + +import java.security.interfaces.RSAPublicKey; import java.util.HashSet; +import java.util.List; import java.util.Set; import static com.topcoder.onlinereview.component.util.SpringUtils.getBean; @@ -468,4 +485,67 @@ public void setSsoCookieService(SSOCookieService ssoCookieService) { AuthorizationHelper.ssoCookieService = ssoCookieService; } + /** + *

+ * Validate jwt token + *

+ * + * @param token the jwt token + * @throws JWTException if any error occurs + * @return the DecodedJWT result + */ + public static DecodedJWT validateJWTToken(String token) throws JWTException { + if (token == null) { + throw new IllegalArgumentException("token must be specified."); + } + DecodedJWT decodedJWT = null; + // Decode only first to get the algorithm + try { + decodedJWT = JWT.decode(token); + } catch (JWTDecodeException e) { + throw new InvalidTokenException(token, "Error occurred in decoding token. " + e.getLocalizedMessage(), e); + } + String algorithm = decodedJWT.getAlgorithm(); + Algorithm alg = null; + // Create the algorithm + if ("RS256".equals(algorithm)) { + List validIssuers = ConfigHelper.getValidIssuers(); + // Validate the issuer + if (decodedJWT.getIssuer() == null || !validIssuers.contains(decodedJWT.getIssuer())) { + throw new InvalidTokenException(token, "Invalid issuer: " + decodedJWT.getIssuer()); + } + + // Create the JWK provider with caching + JwkProvider urlJwkProvider = new UrlJwkProvider(decodedJWT.getIssuer()); + JwkProvider jwkProvider = new GuavaCachedJwkProvider(urlJwkProvider); + + // Get the public key and create the algorithm + try { + Jwk jwk = jwkProvider.get(decodedJWT.getKeyId()); + RSAPublicKey publicKey = (RSAPublicKey) jwk.getPublicKey(); + + alg = Algorithm.RSA256(publicKey, null); + } catch (Exception e) { + throw new JWTException(token, "Error occurred in creating algorithm. " + e.getLocalizedMessage(), e); + } + } else { + throw new JWTException(token, "Algorithm not supported: " + algorithm); + } + + // Verify + try { + Verification verification = JWT.require(alg); + + JWTVerifier verifier = verification.build(); + decodedJWT = verifier.verify(token); + } catch (TokenExpiredException e) { + throw new TokenExpiredException(token); + } catch (SignatureVerificationException | IllegalStateException e) { + throw new InvalidTokenException(token, "Token is invalid. " + e.getLocalizedMessage(), e); + } catch (Exception e) { + throw new JWTException(token, "Error occurred in verifying token. " + e.getLocalizedMessage(), e); + } + return decodedJWT; + } + } diff --git a/src/main/java/com/cronos/onlinereview/util/ConfigHelper.java b/src/main/java/com/cronos/onlinereview/util/ConfigHelper.java index 38a65953bd..ab460ef369 100644 --- a/src/main/java/com/cronos/onlinereview/util/ConfigHelper.java +++ b/src/main/java/com/cronos/onlinereview/util/ConfigHelper.java @@ -596,6 +596,11 @@ public class ConfigHelper { */ private static final String V3_JWT_AUTHORIZATION_URL = "v3jwt_authorization_url"; + /** + *

A String providing the list of valid issuers

+ */ + private static final String VALID_ISSUERS = "valid_issuers"; + /** * This member variable holds the submitter role id. */ @@ -972,6 +977,11 @@ public class ConfigHelper { */ private static String v3jwtAuthorizationUrl; + /** + *

A List for the valid issuers

+ */ + private static final List validIssuers = new ArrayList(); + /** *

Represents the ssoDomainForV3jwtCookie.

*/ @@ -1662,6 +1672,14 @@ public class ConfigHelper { v2jwtCookieName = cfgMgr.getString(ONLINE_REVIEW_CFG_NS, V2_JWT_COOKIE_NAME); ssoDomainForV3jwtCookie = cfgMgr.getString(ONLINE_REVIEW_CFG_NS, SSO_DOMAIN_FOR_V3_JWT_COOKIE); v3jwtAuthorizationUrl = cfgMgr.getString(ONLINE_REVIEW_CFG_NS, V3_JWT_AUTHORIZATION_URL); + // Read the valid issuers property + String validIssuersProperty = cfgMgr.getString(ONLINE_REVIEW_CFG_NS, VALID_ISSUERS); + if (validIssuersProperty != null && validIssuersProperty.trim().length() != 0) { + String[] validIssuerStrings = validIssuersProperty.split(","); + for (String validIssuer : validIssuerStrings) { + validIssuers.add(validIssuer.trim()); + } + } ConfigManager.Property eventBus = cfgMgr.getPropertyObject(ONLINE_REVIEW_CFG_NS, "event_bus"); contestSubmissionDownloadUrl = eventBus.getValue("contestSubmissionDownloadUrl"); @@ -2535,6 +2553,14 @@ public static String getV3jwtAuthorizationUrl() { return v3jwtAuthorizationUrl; } + /** + * Get valid issuers. + * @return the valid issuers list. + */ + public static List getValidIssuers() { + return validIssuers; + } + /** * Get ssoDomainForV3jwtCookie. * @return the ssoDomainForV3jwtCookie. diff --git a/src/main/resources/applicationConfig.properties b/src/main/resources/applicationConfig.properties index e41532790c..cc8d768180 100644 --- a/src/main/resources/applicationConfig.properties +++ b/src/main/resources/applicationConfig.properties @@ -49,4 +49,5 @@ workday.end_time_minutes=0 workday.locale.language=en workday.locale.country=US -togglz.roles=Admin Super Role,Admin Regular Role \ No newline at end of file +togglz.roles=administrator +togglz.role_key=https://topcoder-dev.com/roles \ No newline at end of file diff --git a/src/main/resources/config.xml b/src/main/resources/config.xml index d6974bc36d..d8d4a2742e 100644 --- a/src/main/resources/config.xml +++ b/src/main/resources/config.xml @@ -2074,6 +2074,9 @@ .topcoder.com + + @valid_issuers@ + diff --git a/token.properties.local b/token.properties.local index a3848931db..26e819651a 100644 --- a/token.properties.local +++ b/token.properties.local @@ -272,4 +272,5 @@ aws_s3_access_key=AKIAIZNXS3HERHTLVQKA aws_s3_secret_key=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 topcoder_event_bus_auth_proxy_server_url=https://auth0proxy.topcoder-dev.com/token -new_auth_url=https://accounts-auth0.topcoder-dev.com \ No newline at end of file +new_auth_url=https://accounts-auth0.topcoder-dev.com +valid_issuers=https://api.topcoder-dev.com,https://api.topcoder.com,https://topcoder-dev.auth0.com/,https://auth.topcoder-dev.com/ \ No newline at end of file