diff --git a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java index d473282994..e962c92fde 100644 --- a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java +++ b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java @@ -37,6 +37,7 @@ public enum ClientPolicyEvent { TOKEN_INTROSPECT, USERINFO_REQUEST, LOGOUT_REQUEST, - BACKCHANNEL_AUTHENTICATION_REQUEST + BACKCHANNEL_AUTHENTICATION_REQUEST, + PUSHED_AUTHORIZATION_REQUEST } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/par/clientpolicy/context/PushedAuthorizationRequestContext.java b/services/src/main/java/org/keycloak/protocol/oidc/par/clientpolicy/context/PushedAuthorizationRequestContext.java new file mode 100644 index 0000000000..da84abcf4f --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/par/clientpolicy/context/PushedAuthorizationRequestContext.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.par.clientpolicy.context; + +import javax.ws.rs.core.MultivaluedMap; + +import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; +import org.keycloak.services.clientpolicy.ClientPolicyContext; +import org.keycloak.services.clientpolicy.ClientPolicyEvent; + +public class PushedAuthorizationRequestContext implements ClientPolicyContext { + + private final MultivaluedMap requestParameters; + private AuthorizationEndpointRequest request; + + public PushedAuthorizationRequestContext(AuthorizationEndpointRequest request, + MultivaluedMap requestParameters) { + this.request = request; + this.requestParameters = requestParameters; + } + + @Override + public ClientPolicyEvent getEvent() { + return ClientPolicyEvent.PUSHED_AUTHORIZATION_REQUEST; + } + + public AuthorizationEndpointRequest getRequest() { + return request; + } + + public MultivaluedMap getRequestParameters() { + return requestParameters; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java index 315b1d063c..7d59ca6422 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java @@ -30,6 +30,8 @@ import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequestParserProcessor; import org.keycloak.protocol.oidc.par.ParResponse; +import org.keycloak.protocol.oidc.par.clientpolicy.context.PushedAuthorizationRequestContext; +import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.resources.Cors; import org.keycloak.utils.ProfileHelper; @@ -136,6 +138,12 @@ public class ParEndpoint extends AbstractParEndpoint { ex.throwAsCorsErrorResponseException(cors); } + try { + session.clientPolicy().triggerOnEvent(new PushedAuthorizationRequestContext(authorizationRequest, httpRequest.getDecodedFormParameters())); + } catch (ClientPolicyException cpe) { + throw throwErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST); + } + Map params = new HashMap<>(); UUID key = UUID.randomUUID(); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java index 4b1d9876eb..a95983a083 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java @@ -52,6 +52,7 @@ public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider{ cRep.setOrigin(VALID_CORS_URL); }); @@ -811,6 +819,45 @@ public class ParTest extends AbstractClientPoliciesTest { } } + @Test + public void testExtendedClientPolicyIntefacesForPar() throws Exception { + // create client dynamically + String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> { + clientRep.setRequirePushedAuthorizationRequests(Boolean.TRUE); + clientRep.setRedirectUris(new ArrayList(Arrays.asList(CLIENT_REDIRECT_URI))); + }); + OIDCClientRepresentation oidcCRep = getClientDynamically(clientId); + String clientSecret = oidcCRep.getClientSecret(); + assertEquals(Boolean.TRUE, oidcCRep.getRequirePushedAuthorizationRequests()); + assertTrue(oidcCRep.getRedirectUris().contains(CLIENT_REDIRECT_URI)); + assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, oidcCRep.getTokenEndpointAuthMethod()); + + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen") + .addExecutor(TestRaiseExeptionExecutorFactory.PROVIDER_ID, null) + .toRepresentation() + ).toString(); + updateProfiles(json); + + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + + // Pushed Authorization Request + oauth.clientId(clientId); + oauth.redirectUri(CLIENT_REDIRECT_URI); + ParResponse response = oauth.doPushedAuthorizationRequest(clientId, clientSecret); + assertEquals(400, response.getStatusCode()); + assertEquals(ClientPolicyEvent.PUSHED_AUTHORIZATION_REQUEST.toString(), response.getError()); + assertEquals("Exception thrown intentionally", response.getErrorDescription()); + } + private void doNormalAuthzProcess(String requestUri, String redirectUrl, String clientId, String clientSecret) { // Authorization Request with request_uri of PAR // remove parameters as query strings of uri