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 5f497a8436..d473282994 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 @@ -36,6 +36,7 @@ public enum ClientPolicyEvent { TOKEN_REVOKE, TOKEN_INTROSPECT, USERINFO_REQUEST, - LOGOUT_REQUEST + LOGOUT_REQUEST, + BACKCHANNEL_AUTHENTICATION_REQUEST } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java index 97446fd07b..b51a403f34 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java @@ -38,6 +38,8 @@ import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthe import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequestParserProcessor; import org.keycloak.protocol.oidc.grants.ciba.resolvers.CIBALoginUserResolver; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.clientpolicy.ClientPolicyException; +import org.keycloak.services.clientpolicy.context.BackchannelAuthenticationRequestContext; import org.keycloak.util.JsonSerialization; import javax.ws.rs.Consumes; @@ -185,6 +187,12 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint { extractAdditionalParams(endpointRequest, request); + try { + session.clientPolicy().triggerOnEvent(new BackchannelAuthenticationRequestContext(endpointRequest, params)); + } catch (ClientPolicyException cpe) { + throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST); + } + return request; } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/BackchannelAuthenticationRequestContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/BackchannelAuthenticationRequestContext.java new file mode 100644 index 0000000000..97d888b1a9 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/BackchannelAuthenticationRequestContext.java @@ -0,0 +1,52 @@ +/* + * 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.services.clientpolicy.context; + +import javax.ws.rs.core.MultivaluedMap; + +import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequest; +import org.keycloak.services.clientpolicy.ClientPolicyContext; +import org.keycloak.services.clientpolicy.ClientPolicyEvent; + +/** + * @author Takashi Norimatsu + */ +public class BackchannelAuthenticationRequestContext implements ClientPolicyContext { + + private final BackchannelAuthenticationEndpointRequest request; + private final MultivaluedMap requestParameters; + + public BackchannelAuthenticationRequestContext(BackchannelAuthenticationEndpointRequest request, + MultivaluedMap requestParameters) { + this.request = request; + this.requestParameters = requestParameters; + } + + @Override + public ClientPolicyEvent getEvent() { + return ClientPolicyEvent.BACKCHANNEL_AUTHENTICATION_REQUEST; + } + + public BackchannelAuthenticationEndpointRequest getRequest() { + return request; + } + + public MultivaluedMap getRequestParameters() { + return requestParameters; + } +} 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 f67d5ebb38..4b1d9876eb 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 @@ -51,6 +51,7 @@ public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { + clientRep.setSecret(clientSecret); + clientRep.setStandardFlowEnabled(Boolean.TRUE); + clientRep.setImplicitFlowEnabled(Boolean.TRUE); + clientRep.setPublicClient(Boolean.FALSE); + clientRep.setBearerOnly(Boolean.FALSE); + Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>()); + attributes.put(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE_PER_CLIENT, "poll"); + attributes.put(CibaConfig.OIDC_CIBA_GRANT_ENABLED, Boolean.TRUE.toString()); + clientRep.setAttributes(attributes); + }); + + // 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); + + AuthenticationRequestAcknowledgement response = oauth.doBackchannelAuthenticationRequest(clientId, clientSecret, TEST_USER_NAME, "Pjb9eD8w", null, null); + assertEquals(400, response.getStatusCode()); + assertEquals(ClientPolicyEvent.BACKCHANNEL_AUTHENTICATION_REQUEST.toString(), response.getError()); + assertEquals("Exception thrown intentionally", response.getErrorDescription()); + } + private void checkMtlsFlow() throws IOException { // Check login. OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);