diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java
new file mode 100644
index 0000000000..bbeae4436c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java
@@ -0,0 +1,68 @@
+/*
+ * 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.executor;
+
+import org.keycloak.OAuthErrorException;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.services.clientpolicy.ClientPolicyContext;
+import org.keycloak.services.clientpolicy.ClientPolicyException;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * @author Takashi Norimatsu
+ */
+public class ConfidentialClientAcceptExecutor implements ClientPolicyExecutorProvider {
+
+ protected final KeycloakSession session;
+
+ public ConfidentialClientAcceptExecutor(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public String getProviderId() {
+ return ConfidentialClientAcceptExecutorFactory.PROVIDER_ID;
+ }
+
+ @Override
+ public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
+ switch (context.getEvent()) {
+ case AUTHORIZATION_REQUEST:
+ case TOKEN_REQUEST:
+ checkIsConfidentialClient();
+ return;
+ default:
+ return;
+ }
+ }
+
+ private void checkIsConfidentialClient() throws ClientPolicyException {
+ ClientModel client = session.getContext().getClient();
+ if (client == null) {
+ throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT, "invalid client access type");
+ }
+ if (client.isPublicClient()) {
+ throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT, "invalid client access type");
+ }
+ if (client.isBearerOnly()) {
+ throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT, "invalid client access type");
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutorFactory.java
new file mode 100644
index 0000000000..3c1fa3af4f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutorFactory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.executor;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.keycloak.Config.Scope;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author Takashi Norimatsu
+ */
+public class ConfidentialClientAcceptExecutorFactory implements ClientPolicyExecutorProviderFactory {
+
+ public static final String PROVIDER_ID = "confidentialclient-accept-executor";
+
+ @Override
+ public ClientPolicyExecutorProvider create(KeycloakSession session) {
+ return new ConfidentialClientAcceptExecutor(session);
+ }
+
+ @Override
+ public void init(Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "On authorization endpoint and token endpoint, this executor checks whether the client is confidential client. If not, it denies its request.";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory
index 852431ab14..c4c6f59825 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory
@@ -6,4 +6,5 @@ org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory
org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory
-org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory
\ No newline at end of file
+org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory
+org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutorFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
index a32c3bbf5e..b1f41550d3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
@@ -116,6 +116,7 @@ import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsCondi
import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory;
import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesCondition;
import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory;
+import org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutor;
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutor;
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory;
import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutor;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
index b2ab94db68..2df5efc7a2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
@@ -81,6 +81,7 @@ import org.keycloak.services.clientpolicy.condition.ClientUpdateContextCondition
import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsConditionFactory;
import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory;
import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory;
+import org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutorFactory;
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory;
import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory;
import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory;
@@ -1511,6 +1512,55 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
}
}
+ @Test
+ public void testConfidentialClientAcceptExecutorExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Erstes Profil", Boolean.FALSE, null)
+ .addExecutor(ConfidentialClientAcceptExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Erstes Politik", Boolean.FALSE, Boolean.TRUE, null, null)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientConfidentialId = generateSuffixedName("confidential-app");
+ String clientConfidentialSecret = "app-secret";
+ String cidConfidential = createClientByAdmin(clientConfidentialId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientConfidentialSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+ adminClient.realm(REALM_NAME).clients().get(cidConfidential).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
+
+ successfulLoginAndLogout(clientConfidentialId, clientConfidentialSecret);
+
+ String clientPublicId = generateSuffixedName("public-app");
+ String cidPublic = createClientByAdmin(clientPublicId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientConfidentialSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.TRUE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+ adminClient.realm(REALM_NAME).clients().get(cidPublic).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
+
+ oauth.clientId(clientPublicId);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_CLIENT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("invalid client access type", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+ }
+
private void checkMtlsFlow() throws IOException {
// Check login.
OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);