Improve details for user error events in OIDC protocol endpoints
Closes #29166 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
This commit is contained in:
parent
32d25f43d0
commit
ba43a10a6d
20 changed files with 235 additions and 91 deletions
|
@ -229,6 +229,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
private void checkClient(String clientId) {
|
private void checkClient(String clientId) {
|
||||||
if (clientId == null) {
|
if (clientId == null) {
|
||||||
|
event.detail(Details.REASON, "Missing parameter: " + OIDCLoginProtocol.CLIENT_ID_PARAM);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
|
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
|
||||||
}
|
}
|
||||||
|
@ -257,8 +258,10 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
|
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
|
||||||
}
|
}
|
||||||
if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||||
|
String errorMessage = "Wrong client protocol.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CLIENT);
|
event.error(Errors.INVALID_CLIENT);
|
||||||
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, "Wrong client protocol.");
|
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getContext().setClient(client);
|
session.getContext().setClient(client);
|
||||||
|
@ -314,6 +317,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
boolean essential = Boolean.parseBoolean(authenticationSession.getClientNote(Constants.FORCE_LEVEL_OF_AUTHENTICATION));
|
boolean essential = Boolean.parseBoolean(authenticationSession.getClientNote(Constants.FORCE_LEVEL_OF_AUTHENTICATION));
|
||||||
if (essential) {
|
if (essential) {
|
||||||
logger.errorf("Requested essential acr value '%s' is not a number and it is not mapped in the ACR-To-Loa mappings of realm or client. Please doublecheck ACR-to-LOA mapping or correct ACR passed in the 'claims' parameter.", acr);
|
logger.errorf("Requested essential acr value '%s' is not a number and it is not mapped in the ACR-To-Loa mappings of realm or client. Please doublecheck ACR-to-LOA mapping or correct ACR passed in the 'claims' parameter.", acr);
|
||||||
|
event.detail(Details.REASON, "Invalid requested essential acr value");
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.CLAIMS_PARAM);
|
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -139,8 +139,10 @@ public class AuthorizationEndpointChecker {
|
||||||
|
|
||||||
if (responseType == null) {
|
if (responseType == null) {
|
||||||
ServicesLogger.LOGGER.missingParameter(OAuth2Constants.RESPONSE_TYPE);
|
ServicesLogger.LOGGER.missingParameter(OAuth2Constants.RESPONSE_TYPE);
|
||||||
|
String errorMessage = "Missing parameter: response_type";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Missing parameter: response_type");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.detail(Details.RESPONSE_TYPE, responseType);
|
event.detail(Details.RESPONSE_TYPE, responseType);
|
||||||
|
@ -148,6 +150,7 @@ public class AuthorizationEndpointChecker {
|
||||||
try {
|
try {
|
||||||
this.parsedResponseType = OIDCResponseType.parse(responseType);
|
this.parsedResponseType = OIDCResponseType.parse(responseType);
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
|
event.detail(Details.REASON, iae.getMessage());
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.UNSUPPORTED_RESPONSE_TYPE, null);
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.UNSUPPORTED_RESPONSE_TYPE, null);
|
||||||
}
|
}
|
||||||
|
@ -157,8 +160,10 @@ public class AuthorizationEndpointChecker {
|
||||||
parsedResponseMode = OIDCResponseMode.parse(request.getResponseMode(), parsedResponseType);
|
parsedResponseMode = OIDCResponseMode.parse(request.getResponseMode(), parsedResponseType);
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
ServicesLogger.LOGGER.invalidParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
|
ServicesLogger.LOGGER.invalidParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
|
||||||
|
String errorMessage = "Invalid parameter: " + OIDCLoginProtocol.RESPONSE_MODE_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Invalid parameter: response_mode");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.detail(Details.RESPONSE_MODE, parsedResponseMode.toString().toLowerCase());
|
event.detail(Details.RESPONSE_MODE, parsedResponseMode.toString().toLowerCase());
|
||||||
|
@ -166,8 +171,10 @@ public class AuthorizationEndpointChecker {
|
||||||
// Disallowed by OIDC specs
|
// Disallowed by OIDC specs
|
||||||
if (parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY) {
|
if (parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY) {
|
||||||
ServicesLogger.LOGGER.responseModeQueryNotAllowed();
|
ServicesLogger.LOGGER.responseModeQueryNotAllowed();
|
||||||
|
String errorMessage = "Response_mode 'query' not allowed for implicit or hybrid flow";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Response_mode 'query' not allowed for implicit or hybrid flow");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.parsedResponseMode = parsedResponseMode;
|
this.parsedResponseMode = parsedResponseMode;
|
||||||
|
@ -176,20 +183,26 @@ public class AuthorizationEndpointChecker {
|
||||||
(!StringUtil.isNotBlank(client.getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG)) ||
|
(!StringUtil.isNotBlank(client.getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG)) ||
|
||||||
!StringUtil.isNotBlank(client.getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC)))) {
|
!StringUtil.isNotBlank(client.getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC)))) {
|
||||||
ServicesLogger.LOGGER.responseModeQueryJwtNotAllowed();
|
ServicesLogger.LOGGER.responseModeQueryJwtNotAllowed();
|
||||||
|
String errorMessage = "Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
|
if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
|
||||||
ServicesLogger.LOGGER.flowNotAllowed("Standard");
|
ServicesLogger.LOGGER.flowNotAllowed("Standard");
|
||||||
|
String errorMessage = "Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new AuthorizationCheckException(Response.Status.UNAUTHORIZED, OAuthErrorException.UNAUTHORIZED_CLIENT, "Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.");
|
throw new AuthorizationCheckException(Response.Status.UNAUTHORIZED, OAuthErrorException.UNAUTHORIZED_CLIENT, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedResponseType.isImplicitOrHybridFlow() && !client.isImplicitFlowEnabled()) {
|
if (parsedResponseType.isImplicitOrHybridFlow() && !client.isImplicitFlowEnabled()) {
|
||||||
ServicesLogger.LOGGER.flowNotAllowed("Implicit");
|
ServicesLogger.LOGGER.flowNotAllowed("Implicit");
|
||||||
|
String errorMessage = "Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new AuthorizationCheckException(Response.Status.UNAUTHORIZED, OAuthErrorException.UNAUTHORIZED_CLIENT, "Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.");
|
throw new AuthorizationCheckException(Response.Status.UNAUTHORIZED, OAuthErrorException.UNAUTHORIZED_CLIENT, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,8 +232,10 @@ public class AuthorizationEndpointChecker {
|
||||||
}
|
}
|
||||||
if (!validScopes) {
|
if (!validScopes) {
|
||||||
ServicesLogger.LOGGER.invalidParameter(OIDCLoginProtocol.SCOPE_PARAM);
|
ServicesLogger.LOGGER.invalidParameter(OIDCLoginProtocol.SCOPE_PARAM);
|
||||||
|
String errorMessage = "Invalid scopes: " + request.getScope();
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_SCOPE, "Invalid scopes: " + request.getScope());
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_SCOPE, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,8 +248,10 @@ public class AuthorizationEndpointChecker {
|
||||||
|
|
||||||
if (parsedResponseType.hasResponseType(OIDCResponseType.ID_TOKEN) && request.getNonce() == null) {
|
if (parsedResponseType.hasResponseType(OIDCResponseType.ID_TOKEN) && request.getNonce() == null) {
|
||||||
ServicesLogger.LOGGER.missingParameter(OIDCLoginProtocol.NONCE_PARAM);
|
ServicesLogger.LOGGER.missingParameter(OIDCLoginProtocol.NONCE_PARAM);
|
||||||
|
String errorMessage = "Missing parameter: " + OIDCLoginProtocol.NONCE_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -270,8 +287,10 @@ public class AuthorizationEndpointChecker {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ServicesLogger.LOGGER.missingParameter(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
ServicesLogger.LOGGER.missingParameter(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
||||||
|
String errorMessage = "Pushed Authorization Request is only allowed.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Pushed Authorization Request is only allowed.");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-4
|
// https://tools.ietf.org/html/rfc7636#section-4
|
||||||
|
@ -292,34 +311,44 @@ public class AuthorizationEndpointChecker {
|
||||||
// check whether code challenge method is specified
|
// check whether code challenge method is specified
|
||||||
if (codeChallengeMethod == null) {
|
if (codeChallengeMethod == null) {
|
||||||
logger.info("PKCE enforced Client without code challenge method.");
|
logger.info("PKCE enforced Client without code challenge method.");
|
||||||
|
String errorMessage = "Missing parameter: " + OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Missing parameter: code_challenge_method");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
// check whether specified code challenge method is configured one in advance
|
// check whether specified code challenge method is configured one in advance
|
||||||
if (!codeChallengeMethod.equals(pkceCodeChallengeMethod)) {
|
if (!codeChallengeMethod.equals(pkceCodeChallengeMethod)) {
|
||||||
logger.info("PKCE enforced Client code challenge method is not configured one.");
|
logger.info("PKCE enforced Client code challenge method is not matching the configured one.");
|
||||||
|
String errorMessage = "Invalid parameter: code challenge method is not matching the configured one";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code challenge method is not configured one");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
// check whether code challenge is specified
|
// check whether code challenge is specified
|
||||||
if (codeChallenge == null) {
|
if (codeChallenge == null) {
|
||||||
logger.info("PKCE supporting Client without code challenge");
|
logger.info("PKCE supporting Client without code challenge");
|
||||||
|
String errorMessage = "Missing parameter: " + OIDCLoginProtocol.CODE_CHALLENGE_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Missing parameter: code_challenge");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
// check whether code challenge is formatted along with the PKCE specification
|
// check whether code challenge is formatted along with the PKCE specification
|
||||||
if (!isValidPkceCodeChallenge(codeChallenge)) {
|
if (!isValidPkceCodeChallenge(codeChallenge)) {
|
||||||
logger.infof("PKCE supporting Client with invalid code challenge specified in PKCE, codeChallenge = %s", codeChallenge);
|
logger.infof("PKCE supporting Client with invalid code challenge specified in PKCE, codeChallenge = %s", codeChallenge);
|
||||||
|
String errorMessage = "Invalid parameter: " + OIDCLoginProtocol.CODE_CHALLENGE_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code_challenge");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkParamsForPkceNotEnforcedClient(String codeChallengeMethod, String pkceCodeChallengeMethod, String codeChallenge) throws AuthorizationCheckException {
|
private void checkParamsForPkceNotEnforcedClient(String codeChallengeMethod, String pkceCodeChallengeMethod, String codeChallenge) throws AuthorizationCheckException {
|
||||||
if (codeChallenge == null && codeChallengeMethod != null) {
|
if (codeChallenge == null && codeChallengeMethod != null) {
|
||||||
logger.info("PKCE supporting Client without code challenge");
|
logger.info("PKCE supporting Client without code challenge");
|
||||||
|
String errorMessage = "Missing parameter: " + OIDCLoginProtocol.CODE_CHALLENGE_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Missing parameter: code_challenge");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on code_challenge value decide whether this client(RP) supports PKCE
|
// based on code_challenge value decide whether this client(RP) supports PKCE
|
||||||
|
@ -333,8 +362,10 @@ public class AuthorizationEndpointChecker {
|
||||||
// plain or S256
|
// plain or S256
|
||||||
if (!codeChallengeMethod.equals(OIDCLoginProtocol.PKCE_METHOD_S256) && !codeChallengeMethod.equals(OIDCLoginProtocol.PKCE_METHOD_PLAIN)) {
|
if (!codeChallengeMethod.equals(OIDCLoginProtocol.PKCE_METHOD_S256) && !codeChallengeMethod.equals(OIDCLoginProtocol.PKCE_METHOD_PLAIN)) {
|
||||||
logger.infof("PKCE supporting Client with invalid code challenge method not specified in PKCE, codeChallengeMethod = %s", codeChallengeMethod);
|
logger.infof("PKCE supporting Client with invalid code challenge method not specified in PKCE, codeChallengeMethod = %s", codeChallengeMethod);
|
||||||
|
String errorMessage = "Invalid parameter: " + OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code_challenge_method");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// https://tools.ietf.org/html/rfc7636#section-4.3
|
// https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
|
@ -344,8 +375,10 @@ public class AuthorizationEndpointChecker {
|
||||||
|
|
||||||
if (!isValidPkceCodeChallenge(codeChallenge)) {
|
if (!isValidPkceCodeChallenge(codeChallenge)) {
|
||||||
logger.infof("PKCE supporting Client with invalid code challenge specified in PKCE, codeChallenge = %s", codeChallenge);
|
logger.infof("PKCE supporting Client with invalid code challenge specified in PKCE, codeChallenge = %s", codeChallenge);
|
||||||
|
String errorMessage = "Invalid parameter: " + OIDCLoginProtocol.CODE_CHALLENGE_PARAM;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code_challenge");
|
throw new AuthorizationCheckException(Response.Status.BAD_REQUEST, OAuthErrorException.INVALID_REQUEST, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,17 +165,20 @@ public class LogoutEndpoint {
|
||||||
if (!providerConfig.isLegacyLogoutRedirectUri()) {
|
if (!providerConfig.isLegacyLogoutRedirectUri()) {
|
||||||
if (deprecatedRedirectUri != null) {
|
if (deprecatedRedirectUri != null) {
|
||||||
event.event(EventType.LOGOUT);
|
event.event(EventType.LOGOUT);
|
||||||
|
String errorMessage = "Parameter 'redirect_uri' no longer supported.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
logger.warnf("Parameter 'redirect_uri' no longer supported. Please use 'post_logout_redirect_uri' with 'id_token_hint' for this endpoint. Alternatively you can enable backwards compatibility option '%s' of oidc login protocol in the server configuration.",
|
logger.warnf("%s Please use 'post_logout_redirect_uri' with 'id_token_hint' for this endpoint. Alternatively you can enable backwards compatibility option '%s' of oidc login protocol in the server configuration.",
|
||||||
OIDCLoginProtocolFactory.CONFIG_LEGACY_LOGOUT_REDIRECT_URI);
|
errorMessage, OIDCLoginProtocolFactory.CONFIG_LEGACY_LOGOUT_REDIRECT_URI);
|
||||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
|
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postLogoutRedirectUri != null && encodedIdToken == null && clientId == null) {
|
if (postLogoutRedirectUri != null && encodedIdToken == null && clientId == null) {
|
||||||
event.event(EventType.LOGOUT);
|
event.event(EventType.LOGOUT);
|
||||||
|
String errorMessage = "Either the parameter 'client_id' or the parameter 'id_token_hint' is required when 'post_logout_redirect_uri' is used.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
logger.warnf(
|
logger.warnf(errorMessage);
|
||||||
"Either the parameter 'client_id' or the parameter 'id_token_hint' is required when 'post_logout_redirect_uri' is used.");
|
|
||||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER,
|
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER,
|
||||||
OIDCLoginProtocol.ID_TOKEN_HINT);
|
OIDCLoginProtocol.ID_TOKEN_HINT);
|
||||||
}
|
}
|
||||||
|
@ -199,6 +202,7 @@ public class LogoutEndpoint {
|
||||||
TokenVerifier.createWithoutSignature(idToken).tokenType(Arrays.asList(TokenUtil.TOKEN_TYPE_ID)).verify();
|
TokenVerifier.createWithoutSignature(idToken).tokenType(Arrays.asList(TokenUtil.TOKEN_TYPE_ID)).verify();
|
||||||
} catch (OAuthErrorException | VerificationException e) {
|
} catch (OAuthErrorException | VerificationException e) {
|
||||||
event.event(EventType.LOGOUT);
|
event.event(EventType.LOGOUT);
|
||||||
|
event.detail(Details.REASON, e.getMessage());
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.ID_TOKEN_HINT);
|
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.ID_TOKEN_HINT);
|
||||||
}
|
}
|
||||||
|
@ -216,8 +220,10 @@ public class LogoutEndpoint {
|
||||||
if (!idToken.getIssuedFor().equals(clientId)) {
|
if (!idToken.getIssuedFor().equals(clientId)) {
|
||||||
event.event(EventType.LOGOUT);
|
event.event(EventType.LOGOUT);
|
||||||
event.client(clientId);
|
event.client(clientId);
|
||||||
|
String errorMessage = "Parameter client_id is different than the client for which ID Token was issued.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
logger.warnf("Parameter client_id is different than the client for which ID Token was issued. Parameter client_id: '%s', ID Token issued for: '%s'.", clientId, idToken.getIssuedFor());
|
logger.warnf("%s Parameter client_id: '%s', ID Token issued for: '%s'.", errorMessage, clientId, idToken.getIssuedFor());
|
||||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.ID_TOKEN_HINT);
|
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.ID_TOKEN_HINT);
|
||||||
} else {
|
} else {
|
||||||
confirmationNeeded = false;
|
confirmationNeeded = false;
|
||||||
|
@ -359,11 +365,13 @@ public class LogoutEndpoint {
|
||||||
checks.initialVerify();
|
checks.initialVerify();
|
||||||
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.LOGGING_OUT.name(), ClientSessionCode.ActionType.USER) || !checks.isActionRequest() || !formData.containsKey("confirmLogout")) {
|
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.LOGGING_OUT.name(), ClientSessionCode.ActionType.USER) || !checks.isActionRequest() || !formData.containsKey("confirmLogout")) {
|
||||||
AuthenticationSessionModel logoutSession = checks.getAuthenticationSession();
|
AuthenticationSessionModel logoutSession = checks.getAuthenticationSession();
|
||||||
logger.debugf("Failed verification during logout. logoutSessionId=%s, clientId=%s, tabId=%s",
|
String errorMessage = "Failed verification during logout.";
|
||||||
logoutSession != null ? logoutSession.getParentSession().getId() : "unknown", clientId, tabId);
|
logger.debugf( "%s logoutSessionId=%s, clientId=%s, tabId=%s",
|
||||||
|
errorMessage, logoutSession != null ? logoutSession.getParentSession().getId() : "unknown", clientId, tabId);
|
||||||
|
|
||||||
SystemClientUtil.checkSkipLink(session, logoutSession);
|
SystemClientUtil.checkSkipLink(session, logoutSession);
|
||||||
|
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.SESSION_EXPIRED);
|
event.error(Errors.SESSION_EXPIRED);
|
||||||
|
|
||||||
return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT);
|
return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT);
|
||||||
|
@ -391,12 +399,14 @@ public class LogoutEndpoint {
|
||||||
SessionCodeChecks checks = new LogoutSessionCodeChecks(realm, session.getContext().getUri(), request, clientConnection, session, event, null, clientId, tabId);
|
SessionCodeChecks checks = new LogoutSessionCodeChecks(realm, session.getContext().getUri(), request, clientConnection, session, event, null, clientId, tabId);
|
||||||
AuthenticationSessionModel logoutSession = checks.initialVerifyAuthSession();
|
AuthenticationSessionModel logoutSession = checks.initialVerifyAuthSession();
|
||||||
if (logoutSession == null) {
|
if (logoutSession == null) {
|
||||||
logger.debugf("Failed verification when changing locale logout. clientId=%s, tabId=%s", clientId, tabId);
|
String errorMessage = "Failed verification when changing locale during logout.";
|
||||||
|
logger.debugf("%s clientId=%s, tabId=%s", errorMessage, clientId, tabId);
|
||||||
|
|
||||||
SystemClientUtil.checkSkipLink(session, logoutSession);
|
SystemClientUtil.checkSkipLink(session, logoutSession);
|
||||||
|
|
||||||
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
|
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
|
||||||
if (authResult != null) {
|
if (authResult != null) {
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.LOGOUT_FAILED);
|
event.error(Errors.LOGOUT_FAILED);
|
||||||
return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT);
|
return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT);
|
||||||
} else {
|
} else {
|
||||||
|
@ -562,15 +572,19 @@ public class LogoutEndpoint {
|
||||||
|
|
||||||
String encodedLogoutToken = form.getFirst(OAuth2Constants.LOGOUT_TOKEN);
|
String encodedLogoutToken = form.getFirst(OAuth2Constants.LOGOUT_TOKEN);
|
||||||
if (encodedLogoutToken == null) {
|
if (encodedLogoutToken == null) {
|
||||||
|
String errorMessage = "No logout token";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No logout token",
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, errorMessage,
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, realm, encodedLogoutToken);
|
LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, realm, encodedLogoutToken);
|
||||||
if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
|
if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
|
||||||
|
String errorMessage = validationCode.getErrorMessage();
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, validationCode.getErrorMessage(),
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, errorMessage,
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,9 +608,10 @@ public class LogoutEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!backchannelLogoutResponse.getLocalLogoutSucceeded()) {
|
if (!backchannelLogoutResponse.getLocalLogoutSucceeded()) {
|
||||||
|
String errorMessage = "There was an error during the local logout";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.LOGOUT_FAILED);
|
event.error(Errors.LOGOUT_FAILED);
|
||||||
throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR,
|
throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, errorMessage,
|
||||||
"There was an error in the local logout",
|
|
||||||
Response.Status.NOT_IMPLEMENTED);
|
Response.Status.NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,7 @@ public class UserInfoEndpoint {
|
||||||
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
|
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
|
||||||
|
|
||||||
if (tokenForUserInfo.getToken() == null) {
|
if (tokenForUserInfo.getToken() == null) {
|
||||||
|
event.detail(Details.REASON, "Missing token");
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
throw error.unauthorized();
|
throw error.unauthorized();
|
||||||
}
|
}
|
||||||
|
@ -186,8 +187,10 @@ public class UserInfoEndpoint {
|
||||||
token = verifier.verify().getToken();
|
token = verifier.verify().getToken();
|
||||||
|
|
||||||
if (!TokenUtil.hasScope(token.getScope(), OAuth2Constants.SCOPE_OPENID)) {
|
if (!TokenUtil.hasScope(token.getScope(), OAuth2Constants.SCOPE_OPENID)) {
|
||||||
|
String errorMessage = "Missing openid scope";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.ACCESS_DENIED);
|
event.error(Errors.ACCESS_DENIED);
|
||||||
throw error.insufficientScope("Missing openid scope");
|
throw error.insufficientScope(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientModel = realm.getClientByClientId(token.getIssuedFor());
|
clientModel = realm.getClientByClientId(token.getIssuedFor());
|
||||||
|
@ -205,13 +208,16 @@ public class UserInfoEndpoint {
|
||||||
if (clientModel == null) {
|
if (clientModel == null) {
|
||||||
cors.allowAllOrigins();
|
cors.allowAllOrigins();
|
||||||
}
|
}
|
||||||
|
event.detail(Details.REASON, e.getMessage());
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
throw error.invalidToken("Token verification failed");
|
throw error.invalidToken("Token verification failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientModel.getProtocol().equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
if (!clientModel.getProtocol().equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||||
|
String errorMessage = "Wrong client protocol";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CLIENT);
|
event.error(Errors.INVALID_CLIENT);
|
||||||
throw error.invalidToken("Wrong client protocol");
|
throw error.invalidToken(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getContext().setClient(clientModel);
|
session.getContext().setClient(clientModel);
|
||||||
|
@ -243,8 +249,10 @@ public class UserInfoEndpoint {
|
||||||
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3
|
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3
|
||||||
if (OIDCAdvancedConfigWrapper.fromClientModel(clientModel).isUseMtlsHokToken()) {
|
if (OIDCAdvancedConfigWrapper.fromClientModel(clientModel).isUseMtlsHokToken()) {
|
||||||
if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(token, request, session)) {
|
if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(token, request, session)) {
|
||||||
|
String errorMessage = "Client certificate missing, or its thumbprint and one in the refresh token did NOT match";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw error.invalidToken("Client certificate missing, or its thumbprint and one in the refresh token did NOT match");
|
throw error.invalidToken(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,8 +262,10 @@ public class UserInfoEndpoint {
|
||||||
DPoP dPoP = new DPoPUtil.Validator(session).request(request).uriInfo(session.getContext().getUri()).validate();
|
DPoP dPoP = new DPoPUtil.Validator(session).request(request).uriInfo(session.getContext().getUri()).validate();
|
||||||
DPoPUtil.validateBinding(token, dPoP);
|
DPoPUtil.validateBinding(token, dPoP);
|
||||||
} catch (VerificationException ex) {
|
} catch (VerificationException ex) {
|
||||||
event.detail("detail", ex.getMessage()).error(Errors.NOT_ALLOWED);
|
String errorMessage = "DPoP proof and token binding verification failed";
|
||||||
throw error.invalidToken("DPoP proof and token binding verification failed");
|
event.detail(Details.REASON, errorMessage + ": " + ex.getMessage());
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw error.invalidToken(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.connections.httpclient.HttpClientProvider;
|
import org.keycloak.connections.httpclient.HttpClientProvider;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -119,7 +120,9 @@ public class AuthorizationEndpointRequestParserProcessor {
|
||||||
if (clientParam != null && clientParam.size() == 1) {
|
if (clientParam != null && clientParam.size() == 1) {
|
||||||
return clientParam.get(0);
|
return clientParam.get(0);
|
||||||
} else {
|
} else {
|
||||||
logger.warnf("Parameter 'client_id' not present or present multiple times in the HTTP request parameters");
|
String errorMessage = "Parameter 'client_id' not present or present multiple times in the HTTP request parameters";
|
||||||
|
logger.warnf(errorMessage);
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new ErrorPageException(session, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
|
throw new ErrorPageException(session, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
@ -66,8 +67,10 @@ public class AuthorizationCodeGrantType extends OAuth2GrantTypeBase {
|
||||||
|
|
||||||
String code = formParams.getFirst(OAuth2Constants.CODE);
|
String code = formParams.getFirst(OAuth2Constants.CODE);
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
|
String errorMessage = "Missing parameter: " + OAuth2Constants.CODE;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2CodeParser.ParseResult parseResult = OAuth2CodeParser.parseCode(session, code, realm, event);
|
OAuth2CodeParser.ParseResult parseResult = OAuth2CodeParser.parseCode(session, code, realm, event);
|
||||||
|
@ -127,24 +130,32 @@ public class AuthorizationCodeGrantType extends OAuth2GrantTypeBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirectUri != null && !redirectUri.equals(redirectUriParam)) {
|
if (redirectUri != null && !redirectUri.equals(redirectUriParam)) {
|
||||||
event.error(Errors.INVALID_CODE);
|
String errorMessage = "Parameter 'redirect_uri' did not match originally saved redirect URI used in initial OIDC request. Saved redirectUri: %s, redirectUri parameter: %s";
|
||||||
logger.tracef("Parameter 'redirect_uri' did not match originally saved redirect URI used in initial OIDC request. Saved redirectUri: %s, redirectUri parameter: %s", redirectUri, redirectUriParam);
|
event.detail(Details.REASON, String.format(errorMessage, redirectUri, redirectUriParam));
|
||||||
|
event.error(Errors.INVALID_REDIRECT_URI);
|
||||||
|
logger.tracef(errorMessage, redirectUri, redirectUriParam);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Incorrect redirect_uri", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Incorrect redirect_uri", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
|
if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
|
||||||
event.error(Errors.INVALID_CODE);
|
String errorMessage = "Auth error: Found different client_id in clientSession";
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Auth error", Response.Status.BAD_REQUEST);
|
event.detail(Details.REASON, errorMessage);
|
||||||
|
event.error(Errors.INVALID_CLIENT);
|
||||||
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client.isStandardFlowEnabled()) {
|
if (!client.isStandardFlowEnabled()) {
|
||||||
|
String errorMessage = "Client not allowed to exchange code";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||||
|
String errorMessage = "Session not active";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Session not active", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-4.6
|
// https://tools.ietf.org/html/rfc7636#section-4.6
|
||||||
|
@ -182,8 +193,10 @@ public class AuthorizationCodeGrantType extends OAuth2GrantTypeBase {
|
||||||
String scopeParam = codeData.getScope();
|
String scopeParam = codeData.getScope();
|
||||||
Supplier<Stream<ClientScopeModel>> clientScopesSupplier = () -> TokenManager.getRequestedClientScopes(scopeParam, client);
|
Supplier<Stream<ClientScopeModel>> clientScopesSupplier = () -> TokenManager.getRequestedClientScopes(scopeParam, client);
|
||||||
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopesSupplier.get())) {
|
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopesSupplier.get())) {
|
||||||
|
String errorMessage = "Client no longer has requested consent from user";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, scopeParam, session);
|
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, scopeParam, session);
|
||||||
|
|
|
@ -154,6 +154,7 @@ public class ClientCredentialsGrantType extends OAuth2GrantTypeBase {
|
||||||
try {
|
try {
|
||||||
session.clientPolicy().triggerOnEvent(new ServiceAccountTokenResponseContext(formParams, clientSessionCtx.getClientSession(), responseBuilder));
|
session.clientPolicy().triggerOnEvent(new ServiceAccountTokenResponseContext(formParams, clientSessionCtx.getClientSession(), responseBuilder));
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
event.detail(Details.REASON, cpe.getErrorDetail());
|
||||||
event.error(cpe.getError());
|
event.error(cpe.getError());
|
||||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,12 @@ import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
@ -34,12 +39,14 @@ import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.http.HttpRequest;
|
import org.keycloak.http.HttpRequest;
|
||||||
import org.keycloak.http.HttpResponse;
|
import org.keycloak.http.HttpResponse;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.ClientSessionContext;
|
import org.keycloak.models.ClientSessionContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -128,6 +135,7 @@ public abstract class OAuth2GrantTypeBase implements OAuth2GrantType {
|
||||||
try {
|
try {
|
||||||
session.clientPolicy().triggerOnEvent(clientPolicyContextGenerator.apply(responseBuilder));
|
session.clientPolicy().triggerOnEvent(clientPolicyContextGenerator.apply(responseBuilder));
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
event.detail(Details.REASON, cpe.getErrorDetail());
|
||||||
event.error(cpe.getError());
|
event.error(cpe.getError());
|
||||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||||
}
|
}
|
||||||
|
@ -165,9 +173,11 @@ public abstract class OAuth2GrantTypeBase implements OAuth2GrantType {
|
||||||
responseBuilder.getRefreshToken().setConfirmation(confirmation);
|
responseBuilder.getRefreshToken().setConfirmation(confirmation);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
String errorMessage = "Client Certification missing for MTLS HoK Token Binding";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
|
||||||
"Client Certification missing for MTLS HoK Token Binding", Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +235,7 @@ public abstract class OAuth2GrantTypeBase implements OAuth2GrantType {
|
||||||
dPoP = new DPoPUtil.Validator(session).request(request).uriInfo(session.getContext().getUri()).validate();
|
dPoP = new DPoPUtil.Validator(session).request(request).uriInfo(session.getContext().getUri()).validate();
|
||||||
session.setAttribute(DPoPUtil.DPOP_SESSION_ATTRIBUTE, dPoP);
|
session.setAttribute(DPoPUtil.DPOP_SESSION_ATTRIBUTE, dPoP);
|
||||||
} catch (VerificationException ex) {
|
} catch (VerificationException ex) {
|
||||||
|
event.detail(Details.REASON, ex.getMessage());
|
||||||
event.error(Errors.INVALID_DPOP_PROOF);
|
event.error(Errors.INVALID_DPOP_PROOF);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_DPOP_PROOF, ex.getMessage(), Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_DPOP_PROOF, ex.getMessage(), Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
@ -243,9 +254,10 @@ public abstract class OAuth2GrantTypeBase implements OAuth2GrantType {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validScopes) {
|
if (!validScopes) {
|
||||||
|
String errorMessage = "Invalid scopes: " + scope;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Invalid scopes: " + scope,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
Response.Status.BAD_REQUEST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return scope;
|
return scope;
|
||||||
|
|
|
@ -136,8 +136,10 @@ public class PermissionGrantType extends OAuth2GrantTypeBase {
|
||||||
if (rpt != null) {
|
if (rpt != null) {
|
||||||
AccessToken accessToken = session.tokens().decode(rpt, AccessToken.class);
|
AccessToken accessToken = session.tokens().decode(rpt, AccessToken.class);
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
|
String errorMessage = "RPT signature is invalid";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new CorsErrorResponseException(cors, "invalid_rpt", "RPT signature is invalid", Response.Status.FORBIDDEN);
|
throw new CorsErrorResponseException(cors, "invalid_rpt", errorMessage, Response.Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
authorizationRequest.setRpt(accessToken);
|
authorizationRequest.setRpt(accessToken);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.SecretGenerator;
|
import org.keycloak.common.util.SecretGenerator;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
@ -52,9 +53,11 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase {
|
||||||
String code = formParams.getFirst(OAuth2Constants.CODE);
|
String code = formParams.getFirst(OAuth2Constants.CODE);
|
||||||
|
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
|
String errorMessage = "Missing parameter: " + OAuth2Constants.CODE;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
|
||||||
"Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
OAuth2CodeParser.ParseResult result = OAuth2CodeParser.parseCode(session, code, realm, event);
|
OAuth2CodeParser.ParseResult result = OAuth2CodeParser.parseCode(session, code, realm, event);
|
||||||
if (result.isIllegalCode()) {
|
if (result.isIllegalCode()) {
|
||||||
|
|
|
@ -66,18 +66,23 @@ public class ResourceOwnerPasswordCredentialsGrantType extends OAuth2GrantTypeBa
|
||||||
event.detail(Details.AUTH_METHOD, "oauth_credentials");
|
event.detail(Details.AUTH_METHOD, "oauth_credentials");
|
||||||
|
|
||||||
if (!client.isDirectAccessGrantsEnabled()) {
|
if (!client.isDirectAccessGrantsEnabled()) {
|
||||||
|
String errorMessage = "Client not allowed for direct access grants";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.UNAUTHORIZED_CLIENT, "Client not allowed for direct access grants", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.UNAUTHORIZED_CLIENT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.isConsentRequired()) {
|
if (client.isConsentRequired()) {
|
||||||
|
String errorMessage = "Client requires user consent";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.CONSENT_DENIED);
|
event.error(Errors.CONSENT_DENIED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_CLIENT, "Client requires user consent", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_CLIENT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.clientPolicy().triggerOnEvent(new ResourceOwnerPasswordCredentialsContext(formParams));
|
session.clientPolicy().triggerOnEvent(new ResourceOwnerPasswordCredentialsContext(formParams));
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
event.detail(Details.REASON, cpe.getErrorDetail());
|
||||||
event.error(cpe.getError());
|
event.error(cpe.getError());
|
||||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||||
}
|
}
|
||||||
|
@ -116,8 +121,10 @@ public class ResourceOwnerPasswordCredentialsGrantType extends OAuth2GrantTypeBa
|
||||||
if (user.getRequiredActionsStream().count() > 0 || authSession.getRequiredActions().size() > 0) {
|
if (user.getRequiredActionsStream().count() > 0 || authSession.getRequiredActions().size() > 0) {
|
||||||
// Remove authentication session as "Resource Owner Password Credentials Grant" is single-request scoped authentication
|
// Remove authentication session as "Resource Owner Password Credentials Grant" is single-request scoped authentication
|
||||||
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, false);
|
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, false);
|
||||||
|
String errorMessage = "Account is not fully set up";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.RESOLVE_REQUIRED_ACTIONS);
|
event.error(Errors.RESOLVE_REQUIRED_ACTIONS);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Account is not fully set up", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +151,7 @@ public class ResourceOwnerPasswordCredentialsGrantType extends OAuth2GrantTypeBa
|
||||||
try {
|
try {
|
||||||
session.clientPolicy().triggerOnEvent(new ResourceOwnerPasswordCredentialsResponseContext(formParams, clientSessionCtx, responseBuilder));
|
session.clientPolicy().triggerOnEvent(new ResourceOwnerPasswordCredentialsResponseContext(formParams, clientSessionCtx, responseBuilder));
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
event.detail(Details.REASON, cpe.getErrorDetail());
|
||||||
event.error(cpe.getError());
|
event.error(cpe.getError());
|
||||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,16 +113,20 @@ public class CibaGrantType extends OAuth2GrantTypeBase {
|
||||||
setContext(context);
|
setContext(context);
|
||||||
|
|
||||||
if (!realm.getCibaPolicy().isOIDCCIBAGrantEnabled(client)) {
|
if (!realm.getCibaPolicy().isOIDCCIBAGrantEnabled(client)) {
|
||||||
|
String errorMessage = "Client not allowed OIDC CIBA Grant";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT,
|
||||||
"Client not allowed OIDC CIBA Grant", Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
String jwe = formParams.getFirst(AUTH_REQ_ID);
|
String jwe = formParams.getFirst(AUTH_REQ_ID);
|
||||||
|
|
||||||
if (jwe == null) {
|
if (jwe == null) {
|
||||||
|
String errorMessage = "Missing parameter: " + AUTH_REQ_ID;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + AUTH_REQ_ID, Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.tracev("CIBA Grant :: authReqId = {0}", jwe);
|
logger.tracev("CIBA Grant :: authReqId = {0}", jwe);
|
||||||
|
@ -185,8 +189,10 @@ public class CibaGrantType extends OAuth2GrantTypeBase {
|
||||||
if (!TokenManager
|
if (!TokenManager
|
||||||
.verifyConsentStillAvailable(session,
|
.verifyConsentStillAvailable(session,
|
||||||
user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
|
user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
|
||||||
|
String errorMessage = "Client no longer has requested consent from user";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext
|
ClientSessionContext clientSessionCtx = DefaultClientSessionContext
|
||||||
|
@ -236,8 +242,10 @@ public class CibaGrantType extends OAuth2GrantTypeBase {
|
||||||
authSession.setAuthenticatedUser(user);
|
authSession.setAuthenticatedUser(user);
|
||||||
|
|
||||||
if (user.getRequiredActionsStream().count() > 0) {
|
if (user.getRequiredActionsStream().count() > 0) {
|
||||||
|
String errorMessage = "Account is not fully set up";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.RESOLVE_REQUIRED_ACTIONS);
|
event.error(Errors.RESOLVE_REQUIRED_ACTIONS);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Account is not fully set up", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(authSession);
|
||||||
|
|
|
@ -157,8 +157,10 @@ public class DeviceGrantType extends OAuth2GrantTypeBase {
|
||||||
|
|
||||||
if (deviceCodeModel != null) {
|
if (deviceCodeModel != null) {
|
||||||
if (!client.getClientId().equals(deviceCodeModel.getClientId())) {
|
if (!client.getClientId().equals(deviceCodeModel.getClientId())) {
|
||||||
|
String errorMessage = "unauthorized client";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
|
event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "unauthorized client",
|
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, errorMessage,
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,16 +212,20 @@ public class DeviceGrantType extends OAuth2GrantTypeBase {
|
||||||
setContext(context);
|
setContext(context);
|
||||||
|
|
||||||
if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
|
if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
|
||||||
|
String errorMessage = "Client not allowed OAuth 2.0 Device Authorization Grant";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT,
|
||||||
"Client not allowed OAuth 2.0 Device Authorization Grant", Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
String deviceCode = formParams.getFirst(OAuth2Constants.DEVICE_CODE);
|
String deviceCode = formParams.getFirst(OAuth2Constants.DEVICE_CODE);
|
||||||
if (deviceCode == null) {
|
if (deviceCode == null) {
|
||||||
|
String errorMessage = "Missing parameter: " + OAuth2Constants.DEVICE_CODE;
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
|
event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
|
||||||
"Missing parameter: " + OAuth2Constants.DEVICE_CODE, Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2DeviceCodeModel deviceCodeModel = getDeviceByDeviceCode(session, realm, client, event, deviceCode);
|
OAuth2DeviceCodeModel deviceCodeModel = getDeviceByDeviceCode(session, realm, client, event, deviceCode);
|
||||||
|
@ -242,9 +248,11 @@ public class DeviceGrantType extends OAuth2GrantTypeBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceCodeModel.isDenied()) {
|
if (deviceCodeModel.isDenied()) {
|
||||||
|
String errorMessage = "The end user denied the authorization request";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.ACCESS_DENIED);
|
event.error(Errors.ACCESS_DENIED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED,
|
||||||
"The end user denied the authorization request", Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceCodeModel.isPending()) {
|
if (deviceCodeModel.isPending()) {
|
||||||
|
@ -309,14 +317,17 @@ public class DeviceGrantType extends OAuth2GrantTypeBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||||
|
String errorMessage = "Session not active";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Session not active",
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage,
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.clientPolicy().triggerOnEvent(new DeviceTokenRequestContext(deviceCodeModel, formParams));
|
session.clientPolicy().triggerOnEvent(new DeviceTokenRequestContext(deviceCodeModel, formParams));
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
event.detail(Details.REASON, cpe.getErrorDetail());
|
||||||
event.error(cpe.getError());
|
event.error(cpe.getError());
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(),
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(),
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
|
@ -326,9 +337,11 @@ public class DeviceGrantType extends OAuth2GrantTypeBase {
|
||||||
// (but in device_code-to-token request, it could just theoretically happen that they are not available)
|
// (but in device_code-to-token request, it could just theoretically happen that they are not available)
|
||||||
String scopeParam = deviceCodeModel.getScope();
|
String scopeParam = deviceCodeModel.getScope();
|
||||||
if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
|
if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
|
||||||
|
String errorMessage = "Client no longer has requested consent from user";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE,
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE,
|
||||||
"Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession,
|
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession,
|
||||||
|
|
|
@ -115,6 +115,7 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe
|
||||||
httpRequest.getDecodedFormParameters(), AuthorizationEndpointRequestParserProcessor.EndpointType.OAUTH2_DEVICE_ENDPOINT);
|
httpRequest.getDecodedFormParameters(), AuthorizationEndpointRequestParserProcessor.EndpointType.OAUTH2_DEVICE_ENDPOINT);
|
||||||
|
|
||||||
if (request.getInvalidRequestMessage() != null) {
|
if (request.getInvalidRequestMessage() != null) {
|
||||||
|
event.detail(Details.REASON, request.getInvalidRequestMessage());
|
||||||
event.error(Errors.INVALID_REQUEST);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT,
|
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT,
|
||||||
request.getInvalidRequestMessage(), Response.Status.BAD_REQUEST);
|
request.getInvalidRequestMessage(), Response.Status.BAD_REQUEST);
|
||||||
|
@ -128,9 +129,11 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe
|
||||||
CacheControlUtil.noBackButtonCacheControlHeader(session);
|
CacheControlUtil.noBackButtonCacheControlHeader(session);
|
||||||
|
|
||||||
if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
|
if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
|
||||||
|
String errorMessage = "Client not allowed for OAuth 2.0 Device Authorization Grant";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT,
|
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT,
|
||||||
"Client not allowed for OAuth 2.0 Device Authorization Grant", Response.Status.BAD_REQUEST);
|
errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-4
|
// https://tools.ietf.org/html/rfc7636#section-4
|
||||||
|
@ -314,8 +317,8 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe
|
||||||
* @return Verification page response with error message
|
* @return Verification page response with error message
|
||||||
*/
|
*/
|
||||||
private Response invalidUserCodeResponse(String errorMessage, String reason) {
|
private Response invalidUserCodeResponse(String errorMessage, String reason) {
|
||||||
event.error(Errors.INVALID_OAUTH2_USER_CODE);
|
|
||||||
event.detail(Details.REASON, reason);
|
event.detail(Details.REASON, reason);
|
||||||
|
event.error(Errors.INVALID_OAUTH2_USER_CODE);
|
||||||
logger.debugf("invalid user code: %s", reason);
|
logger.debugf("invalid user code: %s", reason);
|
||||||
return createVerificationPage(errorMessage);
|
return createVerificationPage(errorMessage);
|
||||||
}
|
}
|
||||||
|
@ -394,13 +397,17 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
|
if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
|
||||||
|
String errorMessage = "Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, "Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.isBearerOnly()) {
|
if (client.isBearerOnly()) {
|
||||||
|
String errorMessage = "Bearer-only applications are not allowed to initiate browser login.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, "Bearer-only applications are not allowed to initiate browser login.", Response.Status.FORBIDDEN);
|
throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, errorMessage, Response.Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
String protocol = client.getProtocol();
|
String protocol = client.getProtocol();
|
||||||
|
@ -410,8 +417,10 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe
|
||||||
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
|
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
|
||||||
}
|
}
|
||||||
if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||||
|
String errorMessage = "Wrong client protocol.";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CLIENT);
|
event.error(Errors.INVALID_CLIENT);
|
||||||
throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, "Wrong client protocol." , Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getContext().setClient(client);
|
session.getContext().setClient(client);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.SecretGenerator;
|
import org.keycloak.common.util.SecretGenerator;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.services.CorsErrorResponseException;
|
import org.keycloak.services.CorsErrorResponseException;
|
||||||
|
@ -70,24 +71,27 @@ public class PkceUtils {
|
||||||
public static void checkParamsForPkceEnforcedClient(String codeVerifier, String codeChallenge, String codeChallengeMethod, String authUserId, String authUsername, EventBuilder event, Cors cors) {
|
public static void checkParamsForPkceEnforcedClient(String codeVerifier, String codeChallenge, String codeChallengeMethod, String authUserId, String authUsername, EventBuilder event, Cors cors) {
|
||||||
// check whether code verifier is specified
|
// check whether code verifier is specified
|
||||||
if (codeVerifier == null) {
|
if (codeVerifier == null) {
|
||||||
logger.warnf("PKCE code verifier not specified, authUserId = %s, authUsername = %s", authUserId, authUsername);
|
String errorMessage = "PKCE code verifier not specified";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.CODE_VERIFIER_MISSING);
|
event.error(Errors.CODE_VERIFIER_MISSING);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE code verifier not specified", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
verifyCodeVerifier(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername, event, cors);
|
verifyCodeVerifier(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername, event, cors);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkParamsForPkceNotEnforcedClient(String codeVerifier, String codeChallenge, String codeChallengeMethod, String authUserId, String authUsername, EventBuilder event, Cors cors) {
|
public static void checkParamsForPkceNotEnforcedClient(String codeVerifier, String codeChallenge, String codeChallengeMethod, String authUserId, String authUsername, EventBuilder event, Cors cors) {
|
||||||
if (codeChallenge != null && codeVerifier == null) {
|
if (codeChallenge != null && codeVerifier == null) {
|
||||||
logger.warnf("PKCE code verifier not specified, authUserId = %s, authUsername = %s", authUserId, authUsername);
|
String errorMessage = "PKCE code verifier not specified";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.CODE_VERIFIER_MISSING);
|
event.error(Errors.CODE_VERIFIER_MISSING);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE code verifier not specified", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codeChallenge == null && codeVerifier != null) {
|
if (codeChallenge == null && codeVerifier != null) {
|
||||||
logger.warnf("PKCE code verifier specified but challenge not present in authorization, authUserId = %s, authUsername = %s", authUserId, authUsername);
|
String errorMessage = "PKCE code verifier specified but challenge not present in authorization";
|
||||||
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.INVALID_CODE_VERIFIER);
|
event.error(Errors.INVALID_CODE_VERIFIER);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE code verifier specified but challenge not present in authorization", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codeChallenge != null) {
|
if (codeChallenge != null) {
|
||||||
|
@ -99,9 +103,11 @@ public class PkceUtils {
|
||||||
// check whether code verifier is formatted along with the PKCE specification
|
// check whether code verifier is formatted along with the PKCE specification
|
||||||
|
|
||||||
if (!isValidPkceCodeVerifier(codeVerifier)) {
|
if (!isValidPkceCodeVerifier(codeVerifier)) {
|
||||||
logger.infof("PKCE invalid code verifier");
|
String errorReason = "Invalid code verifier";
|
||||||
|
String errorMessage = "PKCE verification failed: " + errorReason;
|
||||||
|
event.detail(Details.REASON, errorReason);
|
||||||
event.error(Errors.INVALID_CODE_VERIFIER);
|
event.error(Errors.INVALID_CODE_VERIFIER);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE invalid code verifier", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debugf("PKCE supporting Client, codeVerifier = %s", codeVerifier);
|
logger.debugf("PKCE supporting Client, codeVerifier = %s", codeVerifier);
|
||||||
|
@ -117,14 +123,18 @@ public class PkceUtils {
|
||||||
codeVerifierEncoded = codeVerifier;
|
codeVerifierEncoded = codeVerifier;
|
||||||
}
|
}
|
||||||
} catch (Exception nae) {
|
} catch (Exception nae) {
|
||||||
logger.infof("PKCE code verification failed, not supported algorithm specified");
|
String errorReason = "Unsupported algorithm specified";
|
||||||
|
String errorMessage = "PKCE verification failed: " + errorReason;
|
||||||
|
event.detail(Details.REASON, errorReason);
|
||||||
event.error(Errors.PKCE_VERIFICATION_FAILED);
|
event.error(Errors.PKCE_VERIFICATION_FAILED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE code verification failed, not supported algorithm specified", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
if (!codeChallenge.equals(codeVerifierEncoded)) {
|
if (!codeChallenge.equals(codeVerifierEncoded)) {
|
||||||
logger.warnf("PKCE verification failed. authUserId = %s, authUsername = %s", authUserId, authUsername);
|
String errorReason = "Code mismatch";
|
||||||
|
String errorMessage = "PKCE verification failed: " + errorReason;
|
||||||
|
event.detail(Details.REASON, errorReason);
|
||||||
event.error(Errors.PKCE_VERIFICATION_FAILED);
|
event.error(Errors.PKCE_VERIFICATION_FAILED);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE verification failed", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
} else {
|
} else {
|
||||||
logger.debugf("PKCE verification success. codeVerifierEncoded = %s, codeChallenge = %s", codeVerifierEncoded, codeChallenge);
|
logger.debugf("PKCE verification success. codeVerifierEncoded = %s, codeChallenge = %s", codeVerifierEncoded, codeChallenge);
|
||||||
}
|
}
|
||||||
|
@ -132,11 +142,11 @@ public class PkceUtils {
|
||||||
|
|
||||||
private static boolean isValidPkceCodeVerifier(String codeVerifier) {
|
private static boolean isValidPkceCodeVerifier(String codeVerifier) {
|
||||||
if (codeVerifier.length() < OIDCLoginProtocol.PKCE_CODE_VERIFIER_MIN_LENGTH) {
|
if (codeVerifier.length() < OIDCLoginProtocol.PKCE_CODE_VERIFIER_MIN_LENGTH) {
|
||||||
logger.infof(" Error: PKCE codeVerifier length under lower limit , codeVerifier = %s", codeVerifier);
|
logger.debugf(" Error: PKCE codeVerifier length under lower limit , codeVerifier = %s", codeVerifier);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (codeVerifier.length() > OIDCLoginProtocol.PKCE_CODE_VERIFIER_MAX_LENGTH) {
|
if (codeVerifier.length() > OIDCLoginProtocol.PKCE_CODE_VERIFIER_MAX_LENGTH) {
|
||||||
logger.infof(" Error: PKCE codeVerifier length over upper limit , codeVerifier = %s", codeVerifier);
|
logger.debugf(" Error: PKCE codeVerifier length over upper limit , codeVerifier = %s", codeVerifier);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Matcher m = VALID_CODE_VERIFIER_PATTERN.matcher(codeVerifier);
|
Matcher m = VALID_CODE_VERIFIER_PATTERN.matcher(codeVerifier);
|
||||||
|
|
|
@ -144,7 +144,7 @@ public class PKCEEnforcerExecutor implements ClientPolicyExecutorProvider<PKCEEn
|
||||||
|
|
||||||
// check whether specified code challenge method is configured one in advance
|
// check whether specified code challenge method is configured one in advance
|
||||||
if (pkceCodeChallengeMethod != null && !codeChallengeMethod.equals(pkceCodeChallengeMethod)) {
|
if (pkceCodeChallengeMethod != null && !codeChallengeMethod.equals(pkceCodeChallengeMethod)) {
|
||||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code challenge method is not configured one");
|
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code challenge method is not matching the configured one");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether code challenge is specified
|
// check whether code challenge is specified
|
||||||
|
|
|
@ -596,7 +596,7 @@ public class FAPI1Test extends AbstractFAPITest {
|
||||||
oauth.codeChallenge("234567890_234567890123");
|
oauth.codeChallenge("234567890_234567890123");
|
||||||
oauth.codeChallengeMethod(OAuth2Constants.PKCE_METHOD_PLAIN);
|
oauth.codeChallengeMethod(OAuth2Constants.PKCE_METHOD_PLAIN);
|
||||||
oauth.openLoginForm();
|
oauth.openLoginForm();
|
||||||
assertRedirectedToClientWithError(OAuthErrorException.INVALID_REQUEST,false, "Invalid parameter: code challenge method is not configured one");
|
assertRedirectedToClientWithError(OAuthErrorException.INVALID_REQUEST,false, "Invalid parameter: code challenge method is not matching the configured one");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumption is that clientId is already set in "oauth" client when this method is called. Also assumption is that PKCE parameters are properly set (in case PKCE required for the client)
|
// Assumption is that clientId is already set in "oauth" client when this method is called. Also assumption is that PKCE parameters are properly set (in case PKCE required for the client)
|
||||||
|
|
|
@ -342,7 +342,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
||||||
assertEquals("invalid_grant", response.getError());
|
assertEquals("invalid_grant", response.getError());
|
||||||
assertEquals("Incorrect redirect_uri", response.getErrorDescription());
|
assertEquals("Incorrect redirect_uri", response.getErrorDescription());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_code")
|
events.expectCodeToToken(codeId, loginEvent.getSessionId()).error(Errors.INVALID_REDIRECT_URI)
|
||||||
.removeDetail(Details.TOKEN_ID)
|
.removeDetail(Details.TOKEN_ID)
|
||||||
.removeDetail(Details.REFRESH_TOKEN_ID)
|
.removeDetail(Details.REFRESH_TOKEN_ID)
|
||||||
.removeDetail(Details.REFRESH_TOKEN_TYPE)
|
.removeDetail(Details.REFRESH_TOKEN_TYPE)
|
||||||
|
|
|
@ -448,7 +448,7 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
Assert.assertEquals(400, tokenResponse.getStatusCode());
|
Assert.assertEquals(400, tokenResponse.getStatusCode());
|
||||||
Assert.assertEquals("invalid_grant", tokenResponse.getError());
|
Assert.assertEquals("invalid_grant", tokenResponse.getError());
|
||||||
Assert.assertEquals("PKCE verification failed", tokenResponse.getErrorDescription());
|
Assert.assertEquals("PKCE verification failed: Code mismatch", tokenResponse.getErrorDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
||||||
assertEquals("PKCE verification failed", response.getErrorDescription());
|
assertEquals("PKCE verification failed: Code mismatch", response.getErrorDescription());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, sessionId).error(Errors.PKCE_VERIFICATION_FAILED).clearDetails().assertEvent();
|
events.expectCodeToToken(codeId, sessionId).error(Errors.PKCE_VERIFICATION_FAILED).clearDetails().assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
||||||
assertEquals("PKCE verification failed", response.getErrorDescription());
|
assertEquals("PKCE verification failed: Code mismatch", response.getErrorDescription());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, sessionId).error(Errors.PKCE_VERIFICATION_FAILED).clearDetails().assertEvent();
|
events.expectCodeToToken(codeId, sessionId).error(Errors.PKCE_VERIFICATION_FAILED).clearDetails().assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -298,7 +298,7 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
||||||
assertEquals("PKCE invalid code verifier", response.getErrorDescription());
|
assertEquals("PKCE verification failed: Invalid code verifier", response.getErrorDescription());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
|
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
||||||
assertEquals("PKCE invalid code verifier", response.getErrorDescription());
|
assertEquals("PKCE verification failed: Invalid code verifier", response.getErrorDescription());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
|
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -401,7 +401,7 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
|
||||||
assertEquals("PKCE invalid code verifier", response.getErrorDescription());
|
assertEquals("PKCE verification failed: Invalid code verifier", response.getErrorDescription());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
|
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -619,7 +619,7 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
Assert.assertTrue(errorResponse.isRedirected());
|
Assert.assertTrue(errorResponse.isRedirected());
|
||||||
Assert.assertEquals(errorResponse.getError(), OAuthErrorException.INVALID_REQUEST);
|
Assert.assertEquals(errorResponse.getError(), OAuthErrorException.INVALID_REQUEST);
|
||||||
Assert.assertEquals(errorResponse.getErrorDescription(), "Invalid parameter: code challenge method is not configured one");
|
Assert.assertEquals(errorResponse.getErrorDescription(), "Invalid parameter: code challenge method is not matching the configured one");
|
||||||
|
|
||||||
events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().assertEvent();
|
events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in a new issue