Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pfed-sso: Enable bearer token authentication #811

Merged
merged 14 commits into from
Oct 16, 2023
Merged
13 changes: 12 additions & 1 deletion server/dist/src/main/resources/concord-server.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -549,7 +560,7 @@ concord-server {

# enable to validate token signature
tokenSignatureValidation = false

# JSON as a string
#tokenEncryptionKey = "{}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.List;


public class JwtAuthenticator {

Expand Down Expand Up @@ -78,8 +80,8 @@ public JwtAuthenticator(SsoConfiguration cfg, SsoClient ssoClient) {
* @param token the JWT
* @return <code>true</code> 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);
}

/**
Expand All @@ -89,13 +91,22 @@ public boolean isTokenValid(String token) {
* @param nonce nonce
* @return <code>true</code> 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<String, Object> claims = validateTokenAndGetClaims(token);
if (claims == null) {
return false;
}

if (restrictOnClientId) {
List<String> 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;
}
Expand All @@ -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 <code>null</code> if the JWT is invalid
*/
public String validateTokenAndGetLogin(String token) {
Map<String, Object> claims = validateTokenAndGetClaims(token);
if (claims == null) {
return null;
}
return (String) claims.get("sub");
}

private Map<String, Object> validateTokenAndGetClaims(String token) {

public Map<String, Object> validateTokenAndGetClaims(String token) {
try {
JWT jwt = validateToken(token);
if (jwt == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -178,15 +178,15 @@ 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;
}
HttpURLConnection con = null;
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");
Expand Down Expand Up @@ -240,6 +240,9 @@ public interface Token {
@JsonIgnoreProperties(ignoreUnknown = true)
public interface Profile {

@JsonProperty("sub")
String sub();

@JsonProperty("sAMAccountName")
String userId();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -103,6 +115,10 @@ public class SsoConfiguration implements Serializable {
@Config("sso.autoCreateUsers")
private boolean autoCreateUsers;

@Inject
@Config("sso.pfed.bearerToken.allowedClientIds")
private List<String> allowedClientIds;

public boolean isAutoCreateUsers() {
return autoCreateUsers;
}
Expand Down Expand Up @@ -135,6 +151,14 @@ public String getClientSecret() {
return clientSecret;
}

public boolean getEnableBearerTokens() {
return enableBearerTokens;
}

public boolean getAllowAllClientIds() {
return allowAllClientIds;
}

public String getTokenEncryptionKey() {
return tokenEncryptionKey;
}
Expand Down Expand Up @@ -170,4 +194,9 @@ public boolean isTokenSignatureValidation() {
public String getUserInfoEndpointUrl() {
return userInfoEndpointUrl;
}

public List<String> getAllowedClientIds() {
return allowedClientIds;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
}
Loading