Skip to content

Commit

Permalink
fix(jans-auth-server): redirect when session does not exist but clien…
Browse files Browse the repository at this point in the history
…t_id parameter is present (#6104)

#5942

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>
  • Loading branch information
yuriyz committed Sep 22, 2023
1 parent 0cfc677 commit f8f9591
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,17 @@ public Response requestEndSession(String idTokenHint, String postLogoutRedirectU

errorResponseFactory.validateFeatureEnabled(FeatureFlagType.END_SESSION);

final SessionId sidSession = validateSidRequestParameter(sid, postLogoutRedirectUri, state);
Jwt validatedIdToken = validateIdTokenHint(idTokenHint, sidSession, postLogoutRedirectUri, state);
final SessionId sidSession = validateSidRequestParameter(sid, postLogoutRedirectUri, state, clientId);
Jwt validatedIdToken = validateIdTokenHint(idTokenHint, sidSession, postLogoutRedirectUri, state, clientId);

final Pair<SessionId, AuthorizationGrant> pair = getPair(idTokenHint, validatedIdToken, sid, httpRequest);
if (pair.getFirst() == null) {
final String reason = "Failed to identify session by session_id query parameter or by session_id cookie.";
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, reason, state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, reason, state, clientId));
}

postLogoutRedirectUri = validatePostLogoutRedirectUri(postLogoutRedirectUri, pair, state, clientId);
validateSid(postLogoutRedirectUri, validatedIdToken, pair.getFirst(), state);
validateSid(postLogoutRedirectUri, validatedIdToken, pair.getFirst(), state, clientId);

endSession(pair, httpRequest, httpResponse);
auditLogging(httpRequest, pair);
Expand Down Expand Up @@ -222,14 +222,14 @@ private String addStateInPostLogoutRedirectUri(String postLogoutRedirectUri, Str
.toString();
}

private void validateSid(String postLogoutRedirectUri, Jwt idToken, SessionId session, String state) {
private void validateSid(String postLogoutRedirectUri, Jwt idToken, SessionId session, String state, String clientId) {
if (idToken == null) {
return;
}
final String sid = idToken.getClaims().getClaimAsString("sid");
if (StringUtils.isNotBlank(sid) && !sid.equals(session.getOutsideSid())) {
log.error("sid in id_token_hint does not match sid of the session. id_token_hint sid: {}, session sid: {}", sid, session.getOutsideSid());
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_REQUEST, "sid in id_token_hint does not match sid of the session", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_REQUEST, "sid in id_token_hint does not match sid of the session", state, clientId));
}
}

Expand Down Expand Up @@ -269,10 +269,10 @@ private void backChannel(Map<String, Client> backchannelUris, AuthorizationGrant

}

private Response createErrorResponse(String postLogoutRedirectUri, EndSessionErrorResponseType error, String reason, String state) {
log.debug(reason);
private Response createErrorResponse(String postLogoutRedirectUri, EndSessionErrorResponseType error, String reason, String state, String clientId) {
log.debug("Creating error response, reason: {}, error: {}", reason, error);
try {
if (allowPostLogoutRedirect(postLogoutRedirectUri)) {
if (allowPostLogoutRedirect(postLogoutRedirectUri, clientId)) {
if (ErrorHandlingMethod.REMOTE == appConfiguration.getErrorHandlingMethod()) {
String separator = postLogoutRedirectUri.contains("?") ? "&" : "?";
postLogoutRedirectUri = postLogoutRedirectUri + separator + errorResponseFactory.getErrorAsQueryString(error, "", reason);
Expand All @@ -284,45 +284,60 @@ private Response createErrorResponse(String postLogoutRedirectUri, EndSessionErr
} catch (URISyntaxException e) {
log.error("Can't perform redirect", e);
}

log.trace("Return 400 - error {}, reason {}", error, reason);
return Response.status(Response.Status.BAD_REQUEST).entity(errorResponseFactory.errorAsJson(error, reason)).build();
}

/**
* Allow post logout redirect without validation only if:
* allowPostLogoutRedirectWithoutValidation = true and post_logout_redirect_uri is white listed
*/
private boolean allowPostLogoutRedirect(String postLogoutRedirectUri) {
private boolean allowPostLogoutRedirect(String postLogoutRedirectUri, String clientId) {
if (StringUtils.isBlank(postLogoutRedirectUri)) {
log.trace("Post logout redirect is blank.");
return false;
}

final Boolean allowPostLogoutRedirectWithoutValidation = appConfiguration.getAllowPostLogoutRedirectWithoutValidation();
return allowPostLogoutRedirectWithoutValidation != null &&
final boolean isOk = allowPostLogoutRedirectWithoutValidation != null &&
allowPostLogoutRedirectWithoutValidation &&
endSessionService.isUrlWhiteListed(postLogoutRedirectUri);
if (isOk) {
log.trace("Post logout redirect allowed by 'clientWhiteList' {}", appConfiguration.getClientWhiteList());
return true;
}

if (StringUtils.isNotBlank(clientId) && StringUtils.isNotBlank(redirectionUriService.validatePostLogoutRedirectUri(clientId, postLogoutRedirectUri))) {
log.trace("Post logout redirect allowed by client_id {}", clientId);
return true;
}

log.trace("Post logout redirect is denied.");
return false;
}

private SessionId validateSidRequestParameter(String sid, String postLogoutRedirectUri, String state) {
private SessionId validateSidRequestParameter(String sid, String postLogoutRedirectUri, String state, String clientId) {
// sid is not required but if it is present then we must validate it #831
if (StringUtils.isNotBlank(sid)) {
SessionId sessionIdObject = sessionIdService.getSessionBySid(sid);
if (sessionIdObject == null) {
final String reason = "sid parameter in request is not valid. Logout is rejected. sid parameter in request can be skipped or otherwise valid value must be provided.";
log.error(reason);
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, reason, state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, reason, state, clientId));
}
return sessionIdObject;
}
return null;
}

public Jwt validateIdTokenHint(String idTokenHint, SessionId sidSession, String postLogoutRedirectUri, String state) {
public Jwt validateIdTokenHint(String idTokenHint, SessionId sidSession, String postLogoutRedirectUri, String state, String clientId) {
final boolean isIdTokenHintRequired = isTrue(appConfiguration.getForceIdTokenHintPrecense());

if (isIdTokenHintRequired && StringUtils.isBlank(idTokenHint)) { // must be present for logout tests #1279
final String reason = "id_token_hint is not set";
log.trace(reason);
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_REQUEST, reason, state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_REQUEST, reason, state, clientId));
}

if (StringUtils.isBlank(idTokenHint) && !isIdTokenHintRequired) {
Expand All @@ -337,40 +352,40 @@ public Jwt validateIdTokenHint(String idTokenHint, SessionId sidSession, String
if (tokenHintGrant == null && isRejectEndSessionIfIdTokenExpired) {
final String reason = "id_token_hint is not valid. Logout is rejected. id_token_hint can be skipped or otherwise valid value must be provided.";
log.trace(reason);
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, reason, state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, reason, state, clientId));
}
return validateIdTokenJwt(tokenHintGrant, idTokenHint, sidSession, postLogoutRedirectUri, state);
return validateIdTokenJwt(tokenHintGrant, idTokenHint, sidSession, postLogoutRedirectUri, state, clientId);
}
return null;
}

private Jwt validateIdTokenJwt(AuthorizationGrant tokenHintGrant, String idTokenHint, SessionId sidSession, String postLogoutRedirectUri, String state) {
private Jwt validateIdTokenJwt(AuthorizationGrant tokenHintGrant, String idTokenHint, SessionId sidSession, String postLogoutRedirectUri, String state, String clientId) {
try {
final Jwt jwt = Jwt.parse(idTokenHint);
if (tokenHintGrant != null) { // id_token is in db
log.debug("Found id_token in db.");
return jwt;
}
validateIdTokenSignature(sidSession, jwt, postLogoutRedirectUri, state);
validateIdTokenSignature(sidSession, jwt, postLogoutRedirectUri, state, clientId);
log.debug("id_token is validated successfully.");
return jwt;
} catch (InvalidJwtException e) {
log.error("Unable to parse id_token_hint as JWT.", e);
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "Unable to parse id_token_hint as JWT.", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "Unable to parse id_token_hint as JWT.", state, clientId));
} catch (WebApplicationException e) {
throw e;
} catch (Exception e) {
log.error("Unable to validate id_token_hint as JWT.", e);
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "Unable to validate id_token_hint as JWT.", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "Unable to validate id_token_hint as JWT.", state, clientId));
}
}

private void validateIdTokenSignature(SessionId sidSession, Jwt jwt, String postLogoutRedirectUri, String state) throws InvalidJwtException, CryptoProviderException {
private void validateIdTokenSignature(SessionId sidSession, Jwt jwt, String postLogoutRedirectUri, String state, String clientId) throws InvalidJwtException, CryptoProviderException {
// verify jwt signature if we can't find it in db
if (!cryptoProvider.verifySignature(jwt.getSigningInput(), jwt.getEncodedSignature(), jwt.getHeader().getKeyId(),
null, null, jwt.getHeader().getSignatureAlgorithm())) {
log.error("id_token signature verification failed.");
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "id_token signature verification failed.", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "id_token signature verification failed.", state, clientId));
}

if (isTrue(appConfiguration.getAllowEndSessionWithUnmatchedSid())) {
Expand All @@ -382,7 +397,7 @@ private void validateIdTokenSignature(SessionId sidSession, Jwt jwt, String post
return;
}
log.error("sid claim from id_token does not match to any valid session on AS.");
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "sid claim from id_token does not match to any valid session on AS.", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.INVALID_GRANT_AND_SESSION, "sid claim from id_token does not match to any valid session on AS.", state, clientId));
}

protected AuthorizationGrant getTokenHintGrant(String idTokenHint) {
Expand Down Expand Up @@ -433,18 +448,18 @@ protected String validatePostLogoutRedirectUri(String postLogoutRedirectUri, Pai

if (StringUtils.isBlank(result)) {
log.trace("Failed to validate post_logout_redirect_uri.");
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.POST_LOGOUT_URI_NOT_ASSOCIATED_WITH_CLIENT, "", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.POST_LOGOUT_URI_NOT_ASSOCIATED_WITH_CLIENT, "", state, clientId));
}

if (StringUtils.isNotBlank(result)) {
return result;
}
log.trace("Unable to validate post_logout_redirect_uri.");
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.POST_LOGOUT_URI_NOT_ASSOCIATED_WITH_CLIENT, "", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.POST_LOGOUT_URI_NOT_ASSOCIATED_WITH_CLIENT, "", state, clientId));
} catch (WebApplicationException e) {
if (pair.getFirst() != null) {
log.error(e.getMessage(), e);
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.POST_LOGOUT_URI_NOT_ASSOCIATED_WITH_CLIENT, "", state));
throw new WebApplicationException(createErrorResponse(postLogoutRedirectUri, EndSessionErrorResponseType.POST_LOGOUT_URI_NOT_ASSOCIATED_WITH_CLIENT, "", state, clientId));
} else {
throw e;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,38 +111,38 @@ public void validatePostLogoutRedirectUri_whenValidClientIdIsPassed_shouldValida

@Test
public void validateIdTokenHint_whenIdTokenHintIsBlank_shouldGetNoError() {
assertNull(endSessionRestWebService.validateIdTokenHint("", null, "", "http://postlogout.com"));
assertNull(endSessionRestWebService.validateIdTokenHint("", null, "", "http://postlogout.com", ""));
}

@Test(expectedExceptions = WebApplicationException.class)
public void validateIdTokenHint_whenIdTokenHintIsBlankButRequired_shouldGetError() {
when(appConfiguration.getForceIdTokenHintPrecense()).thenReturn(true);

endSessionRestWebService.validateIdTokenHint("", null, "", "http://postlogout.com");
endSessionRestWebService.validateIdTokenHint("", null, "", "http://postlogout.com", "");
}

@Test(expectedExceptions = WebApplicationException.class)
public void validateIdTokenHint_whenIdTokenIsNotInDbAndExpiredIsNotAllowed_shouldGetError() {
when(appConfiguration.getRejectEndSessionIfIdTokenExpired()).thenReturn(true);
when(endSessionRestWebService.getTokenHintGrant("test")).thenReturn(null);

endSessionRestWebService.validateIdTokenHint("testToken", null, "", "http://postlogout.com");
endSessionRestWebService.validateIdTokenHint("testToken", null, "", "http://postlogout.com", "");
}

@Test(expectedExceptions = WebApplicationException.class)
public void validateIdTokenHint_whenIdTokenIsNotValidJwt_shouldGetError() {
when(appConfiguration.getEndSessionWithAccessToken()).thenReturn(true);
when(endSessionRestWebService.getTokenHintGrant("notValidJwt")).thenReturn(GRANT);

endSessionRestWebService.validateIdTokenHint("notValidJwt", null, "", "http://postlogout.com");
endSessionRestWebService.validateIdTokenHint("notValidJwt", null, "", "http://postlogout.com", "");
}

@Test
public void validateIdTokenHint_whenIdTokenIsValidJwt_shouldGetValidJwt() {
when(appConfiguration.getEndSessionWithAccessToken()).thenReturn(true);
when(endSessionRestWebService.getTokenHintGrant(DUMMY_JWT)).thenReturn(GRANT);

final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, null, "", "http://postlogout.com");
final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, null, "", "http://postlogout.com", "");
assertNotNull(jwt);
}

Expand All @@ -153,7 +153,7 @@ public void validateIdTokenHint_whenIdTokenSignatureIsBad_shouldGetError() throw
when(endSessionRestWebService.getTokenHintGrant(DUMMY_JWT)).thenReturn(null);
when(cryptoProvider.verifySignature(anyString(), anyString(), anyString(), isNull(), isNull(), any())).thenReturn(false);

assertNull(endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, null, "", "http://postlogout.com"));
assertNull(endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, null, "", "http://postlogout.com", ""));
}

@Test
Expand All @@ -163,7 +163,7 @@ public void validateIdTokenHint_whenIdTokenIsExpiredAndSidCheckIsNotRequired_sho
when(endSessionRestWebService.getTokenHintGrant(DUMMY_JWT)).thenReturn(null);
when(cryptoProvider.verifySignature(anyString(), anyString(), isNull(), isNull(), isNull(), any())).thenReturn(true);

final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, null, "", "http://postlogout.com");
final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, null, "", "http://postlogout.com", "");
assertNotNull(jwt);
}

Expand All @@ -177,7 +177,7 @@ public void validateIdTokenHint_whenIdTokenIsExpiredAndSidCheckIsRequired_should
SessionId sidSession = new SessionId();
sidSession.setOutsideSid("1234"); // sid encoded into DUMMY_JWT

final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, sidSession, "", "http://postlogout.com");
final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, sidSession, "", "http://postlogout.com", "");
assertNotNull(jwt);
}

Expand All @@ -191,7 +191,7 @@ public void validateIdTokenHint_whenIdTokenIsExpiredAndSidCheckIsRequiredButSess
SessionId sidSession = new SessionId();
sidSession.setOutsideSid("12345"); // sid encoded into DUMMY_JWT

final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, sidSession, "", "http://postlogout.com");
final Jwt jwt = endSessionRestWebService.validateIdTokenHint(DUMMY_JWT, sidSession, "", "http://postlogout.com", "");
assertNotNull(jwt);
}
}

0 comments on commit f8f9591

Please sign in to comment.