Search in sources :

Example 46 with ClientPolicyException

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);
}
Also used : TokenRequestContext(org.keycloak.services.clientpolicy.context.TokenRequestContext) ServiceAccountTokenRequestContext(org.keycloak.services.clientpolicy.context.ServiceAccountTokenRequestContext) UserSessionModel(org.keycloak.models.UserSessionModel) AuthenticatedClientSessionModel(org.keycloak.models.AuthenticatedClientSessionModel) OAuth2CodeParser(org.keycloak.protocol.oidc.utils.OAuth2CodeParser) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) UserModel(org.keycloak.models.UserModel) DefaultClientSessionContext(org.keycloak.services.util.DefaultClientSessionContext) ClientSessionContext(org.keycloak.models.ClientSessionContext) OAuth2Code(org.keycloak.protocol.oidc.utils.OAuth2Code) Stream(java.util.stream.Stream) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException)

Example 47 with ClientPolicyException

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();
}
Also used : UserSessionModel(org.keycloak.models.UserSessionModel) OAuthErrorException(org.keycloak.OAuthErrorException) TokenRefreshContext(org.keycloak.services.clientpolicy.context.TokenRefreshContext) AuthenticatedClientSessionModel(org.keycloak.models.AuthenticatedClientSessionModel) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) TokenManager(org.keycloak.protocol.oidc.TokenManager) AccessTokenResponse(org.keycloak.representations.AccessTokenResponse) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException)

Example 48 with ClientPolicyException

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.");
}
Also used : ClientModel(org.keycloak.models.ClientModel) ArrayList(java.util.ArrayList) JsonNode(com.fasterxml.jackson.databind.JsonNode) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException)

Example 49 with ClientPolicyException

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;
}
Also used : BackchannelAuthenticationRequestContext(org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelAuthenticationRequestContext) WebApplicationException(javax.ws.rs.WebApplicationException) OAuth2ErrorRepresentation(org.keycloak.representations.idm.OAuth2ErrorRepresentation) CIBAAuthenticationRequest(org.keycloak.protocol.oidc.grants.ciba.channel.CIBAAuthenticationRequest) BackchannelAuthenticationEndpointRequest(org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequest) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) UserModel(org.keycloak.models.UserModel) ClientModel(org.keycloak.models.ClientModel) CibaConfig(org.keycloak.models.CibaConfig) ErrorResponseException(org.keycloak.services.ErrorResponseException)

Example 50 with ClientPolicyException

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();
}
Also used : CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) TokenRevokeContext(org.keycloak.services.clientpolicy.context.TokenRevokeContext) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) POST(javax.ws.rs.POST) Consumes(javax.ws.rs.Consumes)

Aggregations

ClientPolicyException (org.keycloak.services.clientpolicy.ClientPolicyException)62 ClientRepresentation (org.keycloak.representations.idm.ClientRepresentation)23 Test (org.junit.Test)22 OIDCClientRepresentation (org.keycloak.representations.oidc.OIDCClientRepresentation)19 ClientPoliciesBuilder (org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder)14 ClientPolicyBuilder (org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder)14 ClientProfileBuilder (org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder)13 ClientProfilesBuilder (org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder)13 ClientModel (org.keycloak.models.ClientModel)11 ErrorResponseException (org.keycloak.services.ErrorResponseException)10 OAuthErrorException (org.keycloak.OAuthErrorException)9 UserSessionModel (org.keycloak.models.UserSessionModel)9 CorsErrorResponseException (org.keycloak.services.CorsErrorResponseException)9 UserModel (org.keycloak.models.UserModel)8 IOException (java.io.IOException)6 Consumes (javax.ws.rs.Consumes)6 POST (javax.ws.rs.POST)6 Response (javax.ws.rs.core.Response)6 ClientSessionContext (org.keycloak.models.ClientSessionContext)6 RegistrationAuth (org.keycloak.services.clientregistration.policy.RegistrationAuth)6