use of org.keycloak.services.clientpolicy.ClientPolicyException in project keycloak by keycloak.
the class TokenEndpoint method codeToToken.
public Response codeToToken() {
String code = formParams.getFirst(OAuth2Constants.CODE);
if (code == null) {
event.error(Errors.INVALID_CODE);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
}
OAuth2CodeParser.ParseResult parseResult = OAuth2CodeParser.parseCode(session, code, realm, event);
if (parseResult.isIllegalCode()) {
AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
// Attempt to use same code twice should invalidate existing clientSession
if (clientSession != null) {
clientSession.detachFromUserSession();
}
event.error(Errors.INVALID_CODE);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Code not valid", Response.Status.BAD_REQUEST);
}
AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
if (parseResult.isExpiredCode()) {
event.error(Errors.EXPIRED_CODE);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
}
UserSessionModel userSession = null;
if (clientSession != null) {
userSession = clientSession.getUserSession();
}
if (userSession == null) {
event.error(Errors.USER_SESSION_NOT_FOUND);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "User session not found", Response.Status.BAD_REQUEST);
}
UserModel user = userSession.getUser();
if (user == null) {
event.error(Errors.USER_NOT_FOUND);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "User not found", Response.Status.BAD_REQUEST);
}
event.user(userSession.getUser());
if (!user.isEnabled()) {
event.error(Errors.USER_DISABLED);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "User disabled", Response.Status.BAD_REQUEST);
}
OAuth2Code codeData = parseResult.getCodeData();
String redirectUri = codeData.getRedirectUriParam();
String redirectUriParam = formParams.getFirst(OAuth2Constants.REDIRECT_URI);
// KEYCLOAK-4478 Backwards compatibility with the adapters earlier than KC 3.4.2
if (redirectUriParam != null && redirectUriParam.contains("session_state=") && !redirectUri.contains("session_state=")) {
redirectUriParam = KeycloakUriBuilder.fromUri(redirectUriParam).replaceQueryParam(OAuth2Constants.SESSION_STATE, null).build().toString();
}
if (redirectUri != null && !redirectUri.equals(redirectUriParam)) {
event.error(Errors.INVALID_CODE);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Incorrect redirect_uri", Response.Status.BAD_REQUEST);
}
if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
event.error(Errors.INVALID_CODE);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Auth error", Response.Status.BAD_REQUEST);
}
if (!client.isStandardFlowEnabled()) {
event.error(Errors.NOT_ALLOWED);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
}
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
event.error(Errors.USER_SESSION_NOT_FOUND);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Session not active", Response.Status.BAD_REQUEST);
}
// https://tools.ietf.org/html/rfc7636#section-4.6
String codeVerifier = formParams.getFirst(OAuth2Constants.CODE_VERIFIER);
String codeChallenge = codeData.getCodeChallenge();
String codeChallengeMethod = codeData.getCodeChallengeMethod();
String authUserId = user.getId();
String authUsername = user.getUsername();
if (authUserId == null) {
authUserId = "unknown";
}
if (authUsername == null) {
authUsername = "unknown";
}
if (codeChallengeMethod != null && !codeChallengeMethod.isEmpty()) {
PkceUtils.checkParamsForPkceEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername, event, cors);
} else {
// PKCE Activation is OFF, execute the codes implemented in KEYCLOAK-2604
PkceUtils.checkParamsForPkceNotEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername, event, cors);
}
try {
session.clientPolicy().triggerOnEvent(new TokenRequestContext(formParams, parseResult));
} catch (ClientPolicyException cpe) {
event.error(cpe.getError());
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession);
// Compute client scopes again from scope parameter. Check if user still has them granted
// (but in code-to-token request, it could just theoretically happen that they are not available)
String scopeParam = codeData.getScope();
Supplier<Stream<ClientScopeModel>> clientScopesSupplier = () -> TokenManager.getRequestedClientScopes(scopeParam, client);
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopesSupplier.get())) {
event.error(Errors.NOT_ALLOWED);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
}
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, scopeParam, session);
// Set nonce as an attribute in the ClientSessionContext. Will be used for the token generation
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, codeData.getNonce());
return createTokenResponse(user, userSession, clientSessionCtx, scopeParam, true);
}
use of org.keycloak.services.clientpolicy.ClientPolicyException in project keycloak by keycloak.
the class TokenEndpoint method refreshTokenGrant.
public Response refreshTokenGrant() {
String refreshToken = formParams.getFirst(OAuth2Constants.REFRESH_TOKEN);
if (refreshToken == null) {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
}
try {
session.clientPolicy().triggerOnEvent(new TokenRefreshContext(formParams));
} catch (ClientPolicyException cpe) {
event.error(cpe.getError());
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
}
AccessTokenResponse res;
try {
// KEYCLOAK-6771 Certificate Bound Token
TokenManager.RefreshResult result = tokenManager.refreshAccessToken(session, session.getContext().getUri(), clientConnection, realm, client, refreshToken, event, headers, request);
res = result.getResponse();
if (!result.isOfflineToken()) {
UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState());
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession);
}
} catch (OAuthErrorException e) {
logger.trace(e.getMessage(), e);
// KEYCLOAK-6771 Certificate Bound Token
if (MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC.equals(e.getDescription())) {
event.error(Errors.NOT_ALLOWED);
throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.UNAUTHORIZED);
} else {
event.error(Errors.INVALID_TOKEN);
throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
}
}
event.success();
return cors.builder(Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).build();
}
use of org.keycloak.services.clientpolicy.ClientPolicyException in project keycloak by keycloak.
the class SecureCibaSignedAuthenticationRequestExecutor method executeOnBackchannelAuthenticationRequest.
private void executeOnBackchannelAuthenticationRequest(BackchannelAuthenticationEndpointRequest request, MultivaluedMap<String, String> params) throws ClientPolicyException {
logger.trace("Backchannel Authentication Endpoint - authn request");
if (params == null) {
logger.trace("request parameter not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameters");
}
String requestParam = params.getFirst(OIDCLoginProtocol.REQUEST_PARAM);
String requestUriParam = params.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM);
if (requestParam == null && requestUriParam == null) {
logger.trace("signed authentication request not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: 'request' or 'request_uri'");
}
JsonNode signedAuthReq = (JsonNode) session.getAttribute(BackchannelAuthenticationEndpointRequestParser.CIBA_SIGNED_AUTHENTICATION_REQUEST);
// check whether signed authentication request exists
if (signedAuthReq == null || signedAuthReq.isEmpty()) {
logger.trace("signed authentication request not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter: : 'request' or 'request_uri'");
}
// check whether "exp" claim exists
if (signedAuthReq.get("exp") == null) {
logger.trace("exp claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: exp");
}
// check whether signed authentication request not expired
long exp = signedAuthReq.get("exp").asLong();
if (Time.currentTime() > exp) {
// TODO: Time.currentTime() is int while exp is long...
logger.trace("request object expired.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Request Expired");
}
// check whether "nbf" claim exists
if (signedAuthReq.get("nbf") == null) {
logger.trace("nbf claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: nbf");
}
// check whether signed authentication request not yet being processed
long nbf = signedAuthReq.get("nbf").asLong();
if (Time.currentTime() < nbf) {
// TODO: Time.currentTime() is int while nbf is long...
logger.trace("request object not yet being processed.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Request not yet being processed");
}
// check whether signed authentication request's available period is short
int availablePeriod = Optional.ofNullable(configuration.getAvailablePeriod()).orElse(DEFAULT_AVAILABLE_PERIOD).intValue();
if (exp - nbf > availablePeriod) {
logger.trace("signed authentication request's available period is long.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "signed authentication request's available period is long");
}
// check whether "aud" claim exists
List<String> aud = new ArrayList<String>();
JsonNode audience = signedAuthReq.get("aud");
if (audience == null) {
logger.trace("aud claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the 'request' object: aud");
}
if (audience.isArray()) {
for (JsonNode node : audience) aud.add(node.asText());
} else {
aud.add(audience.asText());
}
if (aud.isEmpty()) {
logger.trace("aud claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter value in the 'request' object: aud");
}
// check whether "aud" claim points to this keycloak as authz server
String authzServerIss = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName());
if (!aud.contains(authzServerIss)) {
logger.trace("aud not points to the intended realm.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter in the 'request' object: aud");
}
// check whether "iss" claim exists
if (signedAuthReq.get("iss") == null) {
logger.trace("iss claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the 'request' object: iss");
}
ClientModel client = session.getContext().getClient();
String iss = signedAuthReq.get("iss").asText();
if (!iss.equals(client.getClientId())) {
logger.trace("iss claim not match client's identity.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter in the 'request' object: iss");
}
// check whether "iat" claim exists
if (signedAuthReq.get("iat") == null) {
logger.trace("iat claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: iat");
}
// check whether "jti" claim exists
if (signedAuthReq.get("jti") == null) {
logger.trace("jti claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: jti");
}
logger.trace("Passed.");
}
use of org.keycloak.services.clientpolicy.ClientPolicyException in project keycloak by keycloak.
the class BackchannelAuthenticationEndpoint method authorizeClient.
private CIBAAuthenticationRequest authorizeClient(MultivaluedMap<String, String> params) {
ClientModel client = null;
try {
client = authenticateClient();
} catch (WebApplicationException wae) {
OAuth2ErrorRepresentation errorRep = (OAuth2ErrorRepresentation) wae.getResponse().getEntity();
throw new ErrorResponseException(errorRep.getError(), errorRep.getErrorDescription(), Response.Status.UNAUTHORIZED);
}
BackchannelAuthenticationEndpointRequest endpointRequest = BackchannelAuthenticationEndpointRequestParserProcessor.parseRequest(event, session, client, params, realm.getCibaPolicy());
UserModel user = resolveUser(endpointRequest, realm.getCibaPolicy().getAuthRequestedUserHint());
CIBAAuthenticationRequest request = new CIBAAuthenticationRequest(session, user, client);
request.setClient(client);
String scope = endpointRequest.getScope();
if (scope == null) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "missing parameter : scope", Response.Status.BAD_REQUEST);
}
request.setScope(scope);
// optional parameters
if (endpointRequest.getBindingMessage() != null) {
validateBindingMessage(endpointRequest.getBindingMessage());
request.setBindingMessage(endpointRequest.getBindingMessage());
}
if (endpointRequest.getAcr() != null)
request.setAcrValues(endpointRequest.getAcr());
CibaConfig policy = realm.getCibaPolicy();
// create JWE encoded auth_req_id from Auth Req ID.
Integer expiresIn = Optional.ofNullable(endpointRequest.getRequestedExpiry()).orElse(policy.getExpiresIn());
request.exp(request.getIat() + expiresIn.longValue());
StringBuilder scopes = new StringBuilder(Optional.ofNullable(request.getScope()).orElse(""));
client.getClientScopes(true).forEach((key, value) -> {
if (value.isDisplayOnConsentScreen())
scopes.append(" ").append(value.getName());
});
request.setScope(scopes.toString());
if (endpointRequest.getClientNotificationToken() != null) {
if (!policy.getBackchannelTokenDeliveryMode(client).equals(CibaConfig.CIBA_PING_MODE)) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client Notification token supported only for the ping mode", Response.Status.BAD_REQUEST);
}
if (endpointRequest.getClientNotificationToken().length() > 1024) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client Notification token length is limited to 1024 characters", Response.Status.BAD_REQUEST);
}
request.setClientNotificationToken(endpointRequest.getClientNotificationToken());
}
if (endpointRequest.getClientNotificationToken() == null && policy.getBackchannelTokenDeliveryMode(client).equals(CibaConfig.CIBA_PING_MODE)) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client Notification token needs to be provided with the ping mode", Response.Status.BAD_REQUEST);
}
if (endpointRequest.getUserCode() != null) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "User code not supported", Response.Status.BAD_REQUEST);
}
extractAdditionalParams(endpointRequest, request);
try {
session.clientPolicy().triggerOnEvent(new BackchannelAuthenticationRequestContext(endpointRequest, request, params));
} catch (ClientPolicyException cpe) {
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
return request;
}
use of org.keycloak.services.clientpolicy.ClientPolicyException in project keycloak by keycloak.
the class TokenRevocationEndpoint method revoke.
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response revoke() {
event.event(EventType.REVOKE_GRANT);
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
checkSsl();
checkRealm();
checkClient();
formParams = request.getDecodedFormParameters();
checkParameterDuplicated(formParams);
try {
session.clientPolicy().triggerOnEvent(new TokenRevokeContext(formParams));
} catch (ClientPolicyException cpe) {
event.error(cpe.getError());
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
}
checkToken();
checkIssuedFor();
checkUser();
if (TokenUtil.TOKEN_TYPE_REFRESH.equals(token.getType()) || TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType())) {
revokeClient();
event.detail(Details.REVOKED_CLIENT, client.getClientId());
} else {
revokeAccessToken();
event.detail(Details.TOKEN_ID, token.getId());
}
event.success();
session.getProvider(SecurityHeadersProvider.class).options().allowEmptyContentType();
return cors.builder(Response.ok()).build();
}
Aggregations