diff --git a/server/dist/src/main/resources/concord-server.conf b/server/dist/src/main/resources/concord-server.conf index fe0db4b7fb..e7b0b231ed 100644 --- a/server/dist/src/main/resources/concord-server.conf +++ b/server/dist/src/main/resources/concord-server.conf @@ -533,6 +533,17 @@ concord-server { pfed { enabled = false priority = 0 + + bearerToken { + # enable bearer tokens + enableBearerTokens = false + + # allow all clientIds + allowAllClientIds = false + + # list of allowed pingfed clientids for bearer tokens + allowedClientIds = ["clientId1", "clientId2"] + } } authEndpointUrl = "http://auth.example.com/authorize" tokenEndpointUrl = "http://auth.example.com/token" @@ -549,7 +560,7 @@ concord-server { # enable to validate token signature tokenSignatureValidation = false - + # JSON as a string #tokenEncryptionKey = "{}" diff --git a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/JwtAuthenticator.java b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/JwtAuthenticator.java index 754400733f..63753fee7a 100644 --- a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/JwtAuthenticator.java +++ b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/JwtAuthenticator.java @@ -39,6 +39,8 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.List; + public class JwtAuthenticator { @@ -78,8 +80,8 @@ public JwtAuthenticator(SsoConfiguration cfg, SsoClient ssoClient) { * @param token the JWT * @return true if token valid and not expired */ - public boolean isTokenValid(String token) { - return isTokenValid(token, null); + public boolean isTokenValid(String token, boolean restrictOnClientId) { + return isTokenValid(token, null, restrictOnClientId); } /** @@ -89,13 +91,22 @@ public boolean isTokenValid(String token) { * @param nonce nonce * @return true if token valid, correct nonce and not expired */ - public boolean isTokenValid(String token, String nonce) { + public boolean isTokenValid(String token, String nonce, boolean restrictOnClientId) { try { Map claims = validateTokenAndGetClaims(token); if (claims == null) { return false; } + if (restrictOnClientId) { + List allowedClientIds = cfg.getAllowedClientIds(); + String clientId = (String) claims.get("client_id"); + if(!allowedClientIds.contains(clientId)) { + log.warn("isTokenValid ['{}', '{}'] -> clientId not in allowed list for bearer tokens", token, clientId); + return false; + } + } + if (nonce == null) { return true; } @@ -113,22 +124,7 @@ public boolean isTokenValid(String token, String nonce) { } } - /** - * Validates the token and returns the corresponding user login. - * - * @param token the JWT - * @return corresponding user login or null if the JWT is invalid - */ - public String validateTokenAndGetLogin(String token) { - Map claims = validateTokenAndGetClaims(token); - if (claims == null) { - return null; - } - return (String) claims.get("sub"); - } - - private Map validateTokenAndGetClaims(String token) { - + public Map validateTokenAndGetClaims(String token) { try { JWT jwt = validateToken(token); if (jwt == null) { diff --git a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoAuthFilter.java b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoAuthFilter.java index 2d9051dee6..ddc7c13e1f 100644 --- a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoAuthFilter.java +++ b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoAuthFilter.java @@ -66,7 +66,7 @@ public void doFilter(HttpServletRequest request, HttpServletResponse response, F if (token != null) { if (refreshToken == null){ - boolean isValid = jwtAuthenticator.isTokenValid(token); + boolean isValid = jwtAuthenticator.isTokenValid(token, false); if (isValid) { log.info("doFilter -> found valid token in cookies, redirect to '{}'", from); redirectHelper.sendRedirect(response, from); diff --git a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoClient.java b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoClient.java index 5e4f83dc82..f07a303527 100644 --- a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoClient.java +++ b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoClient.java @@ -126,9 +126,9 @@ public String getTokenSigningKey() throws IOException { } } - public Profile getUserProfile(String refreshToken) throws IOException { + public Profile getUserProfileByRefreshToken(String refreshToken) throws IOException { Token token = getTokenByRefreshToken(refreshToken); - return getProfile(token); + return getProfile(token.accessToken()); } private Token getToken(String urlParameters) throws IOException { @@ -178,7 +178,7 @@ private void postRequest(HttpURLConnection con, String urlParameters) throws IOE } } - private Profile getProfile(Token token) throws IOException { + public Profile getProfile(String accessToken) throws IOException { if (cfg.getUserInfoEndpointUrl() == null) { return null; } @@ -186,7 +186,7 @@ private Profile getProfile(Token token) throws IOException { try { URL url = new URL(cfg.getUserInfoEndpointUrl()); con = (HttpURLConnection) url.openConnection(); - String authzHeaderValue = String.format("Bearer %s", token.accessToken()); + String authzHeaderValue = String.format("Bearer %s", accessToken); con.setRequestProperty(HttpHeaders.AUTHORIZATION, authzHeaderValue); con.setRequestProperty(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_HEADER); con.setRequestMethod("GET"); @@ -240,6 +240,9 @@ public interface Token { @JsonIgnoreProperties(ignoreUnknown = true) public interface Profile { + @JsonProperty("sub") + String sub(); + @JsonProperty("sAMAccountName") String userId(); diff --git a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoConfiguration.java b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoConfiguration.java index 2514ccc426..880e3fce09 100644 --- a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoConfiguration.java +++ b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoConfiguration.java @@ -26,6 +26,10 @@ import javax.inject.Inject; import java.io.Serializable; import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; public class SsoConfiguration implements Serializable { @@ -63,6 +67,14 @@ public class SsoConfiguration implements Serializable { @Config("sso.clientSecret") private String clientSecret; + @Inject + @Config("sso.pfed.bearerToken.enableBearerTokens") + private boolean enableBearerTokens; + + @Inject + @Config("sso.pfed.bearerToken.allowAllClientIds") + private boolean allowAllClientIds; + @Inject @Nullable @Config("sso.tokenSigningKey") @@ -103,6 +115,10 @@ public class SsoConfiguration implements Serializable { @Config("sso.autoCreateUsers") private boolean autoCreateUsers; + @Inject + @Config("sso.pfed.bearerToken.allowedClientIds") + private List allowedClientIds; + public boolean isAutoCreateUsers() { return autoCreateUsers; } @@ -135,6 +151,14 @@ public String getClientSecret() { return clientSecret; } + public boolean getEnableBearerTokens() { + return enableBearerTokens; + } + + public boolean getAllowAllClientIds() { + return allowAllClientIds; + } + public String getTokenEncryptionKey() { return tokenEncryptionKey; } @@ -170,4 +194,9 @@ public boolean isTokenSignatureValidation() { public String getUserInfoEndpointUrl() { return userInfoEndpointUrl; } + + public List getAllowedClientIds() { + return allowedClientIds; + } + } diff --git a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoHandler.java b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoHandler.java index f7aaa47d2a..a7136495ae 100644 --- a/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoHandler.java +++ b/server/plugins/pfed-sso/src/main/java/com/walmartlabs/concord/server/plugins/pfedsso/SsoHandler.java @@ -56,30 +56,34 @@ public AuthenticationToken createToken(ServletRequest request, ServletResponse r HttpServletRequest req = WebUtils.toHttp(request); - String token = SsoCookies.getTokenCookie(req); + String bearerToken = cfg.getEnableBearerTokens() ? extractTokenFromRequest(req) : null; + String token = bearerToken != null ? bearerToken : SsoCookies.getTokenCookie(req); + if (token == null) { return null; } - String login = jwtAuthenticator.validateTokenAndGetLogin(token); - if (login == null) { + boolean restrictOnClientId = (bearerToken != null) && (!cfg.getAllowAllClientIds()); + + if (!jwtAuthenticator.isTokenValid(token, restrictOnClientId)) { return null; } - String[] as = parseDomain(login); - - String refreshToken = SsoCookies.getRefreshCookie(req); - // get userprofile send the response as null if refreshToken is expired or used - SsoClient.Profile profile; try { - profile = ssoClient.getUserProfile(refreshToken); + SsoClient.Profile profile = bearerToken != null ? ssoClient.getProfile(bearerToken) : + ssoClient.getUserProfileByRefreshToken(SsoCookies.getRefreshCookie(req)); + + if (profile == null) { + return null; + } + + String[] as = parseDomain(profile.sub()); + + return new SsoToken(as[0], as[1], profile.displayName(), profile.mail(), profile.userPrincipalName(), profile.nameInNamespace(), profile.groups()); + } catch (IOException e) { return null; } - if (profile == null) { - return null; - } - return new SsoToken(as[0], as[1], profile.displayName(), profile.mail(), profile.userPrincipalName(), profile.nameInNamespace(), profile.groups()); } @Override @@ -112,4 +116,20 @@ private String[] parseDomain(String s) { String domain = s.substring(pos + 1); return new String[]{username, domain}; } + + private String extractTokenFromRequest(HttpServletRequest request) { + final String value = request.getHeader("Authorization"); + + if (value == null || !value.toLowerCase().startsWith("bearer")) { + return null; + } + + String[] parts = value.split(" "); + + if (parts.length < 2) { + return null; + } + + return parts[1].trim(); + } }