From 964cd50f1d2535ef49a1b482d64336d1be3f43f3 Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 19 Oct 2016 12:03:09 +0200 Subject: [PATCH 1/3] KEYCLOAK-3666 Added client reg policies for maxClients and clientDisabled --- .../ComponentValidationException.java | 14 +++- .../ConfigurationValidationHelper.java | 10 +-- .../DefaultClientRegistrationPolicies.java | 8 +- ...lientDisabledClientRegistrationPolicy.java | 72 +++++++++++++++++ ...sabledClientRegistrationPolicyFactory.java | 56 +++++++++++++ .../MaxClientsClientRegistrationPolicy.java | 77 ++++++++++++++++++ ...lientsClientRegistrationPolicyFactory.java | 79 +++++++++++++++++++ .../resources/admin/ComponentResource.java | 26 +++++- ...ion.policy.ClientRegistrationPolicyFactory | 4 +- .../ClientRegistrationPoliciesTest.java | 57 +++++++++++++ .../client/ClientRegistrationTester.java | 54 +++++++++++++ .../messages/admin-messages_en.properties | 3 + 12 files changed, 450 insertions(+), 10 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicy.java create mode 100644 services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicyFactory.java create mode 100644 services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicy.java create mode 100644 services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTester.java diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java b/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java index d1d707c778..09bbc53824 100644 --- a/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java @@ -21,11 +21,15 @@ package org.keycloak.component; * @version $Revision: 1 $ */ public class ComponentValidationException extends RuntimeException { + + private Object[] parameters; + public ComponentValidationException() { } - public ComponentValidationException(String message) { + public ComponentValidationException(String message, Object... parameters) { super(message); + this.parameters = parameters; } public ComponentValidationException(String message, Throwable cause) { @@ -39,4 +43,12 @@ public class ComponentValidationException extends RuntimeException { public ComponentValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } + + public Object[] getParameters() { + return parameters; + } + + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } } diff --git a/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java b/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java index dd9561f517..9a2eea895b 100644 --- a/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java +++ b/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java @@ -49,7 +49,7 @@ public class ConfigurationValidationHelper { try { Integer.parseInt(val); } catch (NumberFormatException e) { - throw new ComponentValidationException(label + " should be a number"); + throw new ComponentValidationException("''{0}'' should be a number", label); } } @@ -68,7 +68,7 @@ public class ConfigurationValidationHelper { try { Long.parseLong(val); } catch (NumberFormatException e) { - throw new ComponentValidationException(label + " should be a number"); + throw new ComponentValidationException("''{0}'' should be a number", label); } } @@ -81,7 +81,7 @@ public class ConfigurationValidationHelper { public ConfigurationValidationHelper checkSingle(String key, String label, boolean required) throws ComponentValidationException { if (model.getConfig().containsKey(key) && model.getConfig().get(key).size() > 1) { - throw new ComponentValidationException(label + " should be a single entry"); + throw new ComponentValidationException("''{0}'' should be a single entry", label); } if (required) { @@ -98,7 +98,7 @@ public class ConfigurationValidationHelper { public ConfigurationValidationHelper checkRequired(String key, String label) throws ComponentValidationException { List values = model.getConfig().get(key); if (values == null) { - throw new ComponentValidationException(label + " is required"); + throw new ComponentValidationException("''{0}'' is required", label); } return this; @@ -113,7 +113,7 @@ public class ConfigurationValidationHelper { String val = model.getConfig().getFirst(key); if (val != null && !(val.equals("true") || val.equals("false"))) { - throw new ComponentValidationException(label + " should be 'true' or 'false'"); + throw new ComponentValidationException("''{0}'' should be 'true' or 'false'", label); } return this; diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/DefaultClientRegistrationPolicies.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/DefaultClientRegistrationPolicies.java index a3d074f142..6fefa93af1 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/DefaultClientRegistrationPolicies.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/DefaultClientRegistrationPolicies.java @@ -33,6 +33,8 @@ import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper; import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper; import org.keycloak.services.clientregistration.policy.impl.ClientTemplatesClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.ConsentRequiredClientRegistrationPolicyFactory; +import org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicy; +import org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.ProtocolMappersClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.ScopeClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrationPolicyFactory; @@ -87,9 +89,13 @@ public class DefaultClientRegistrationPolicies { ComponentModel consentRequiredModel = createModelInstance("Consent Required", realm, ConsentRequiredClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey); realm.addComponentModel(consentRequiredModel); - ComponentModel scopeModel =createModelInstance("Full Scope Disabled", realm, ScopeClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey); + ComponentModel scopeModel = createModelInstance("Full Scope Disabled", realm, ScopeClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey); realm.addComponentModel(scopeModel); + ComponentModel maxClientsModel = createModelInstance("Max Clients Limit", realm, MaxClientsClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey); + maxClientsModel.put(MaxClientsClientRegistrationPolicyFactory.MAX_CLIENTS, MaxClientsClientRegistrationPolicyFactory.DEFAULT_MAX_CLIENTS); + realm.addComponentModel(maxClientsModel); + addGenericPolicies(realm, policyTypeKey); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicy.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicy.java new file mode 100644 index 0000000000..0fd53a39c9 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicy.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 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.clientregistration.policy.impl; + +import org.keycloak.models.ClientModel; +import org.keycloak.services.clientregistration.ClientRegistrationContext; +import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; +import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException; + +/** + * @author Marek Posolda + */ +public class ClientDisabledClientRegistrationPolicy implements ClientRegistrationPolicy { + + @Override + public void beforeRegister(ClientRegistrationContext context) throws ClientRegistrationPolicyException { + + } + + @Override + public void afterRegister(ClientRegistrationContext context, ClientModel clientModel) { + clientModel.setEnabled(false); + } + + @Override + public void beforeUpdate(ClientRegistrationContext context, ClientModel clientModel) throws ClientRegistrationPolicyException { + if (context.getClient().isEnabled() == null) { + return; + } + if (clientModel == null) { + return; + } + + boolean isEnabled = clientModel.isEnabled(); + boolean newEnabled = context.getClient().isEnabled(); + + if (!isEnabled && newEnabled) { + throw new ClientRegistrationPolicyException("Not permitted to enable client"); + } + } + + @Override + public void afterUpdate(ClientRegistrationContext context, ClientModel clientModel) { + + } + + @Override + public void beforeView(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException { + + } + + @Override + public void beforeDelete(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException { + + } +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicyFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicyFactory.java new file mode 100644 index 0000000000..5abf29023e --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientDisabledClientRegistrationPolicyFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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.clientregistration.policy.impl; + +import java.util.Collections; +import java.util.List; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.services.clientregistration.policy.AbstractClientRegistrationPolicyFactory; +import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; + +/** + * @author Marek Posolda + */ +public class ClientDisabledClientRegistrationPolicyFactory extends AbstractClientRegistrationPolicyFactory { + + public static final String PROVIDER_ID = "client-disabled"; + + @Override + public ClientRegistrationPolicy create(KeycloakSession session, ComponentModel model) { + return new ClientDisabledClientRegistrationPolicy(); + } + + @Override + public String getHelpText() { + return "When present, then newly registered client will be disabled and admin needs to manually enable them"; + } + + @Override + public List getConfigProperties() { + return Collections.emptyList(); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicy.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicy.java new file mode 100644 index 0000000000..322bfaf5ec --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicy.java @@ -0,0 +1,77 @@ +/* + * Copyright 2016 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.clientregistration.policy.impl; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.clientregistration.ClientRegistrationContext; +import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; +import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException; + +/** + * @author Marek Posolda + */ +public class MaxClientsClientRegistrationPolicy implements ClientRegistrationPolicy { + + private final KeycloakSession session; + private final ComponentModel componentModel; + + public MaxClientsClientRegistrationPolicy(KeycloakSession session, ComponentModel componentModel) { + this.session = session; + this.componentModel = componentModel; + } + + @Override + public void beforeRegister(ClientRegistrationContext context) throws ClientRegistrationPolicyException { + RealmModel realm = session.getContext().getRealm(); + int currentCount = realm.getClients().size(); + int maxCount = componentModel.get(MaxClientsClientRegistrationPolicyFactory.MAX_CLIENTS, MaxClientsClientRegistrationPolicyFactory.DEFAULT_MAX_CLIENTS); + + if (currentCount >= maxCount) { + throw new ClientRegistrationPolicyException("It's allowed to have max " + maxCount + " clients per realm"); + } + } + + @Override + public void afterRegister(ClientRegistrationContext context, ClientModel clientModel) { + + } + + @Override + public void beforeUpdate(ClientRegistrationContext context, ClientModel clientModel) throws ClientRegistrationPolicyException { + } + + @Override + public void afterUpdate(ClientRegistrationContext context, ClientModel clientModel) { + + } + + @Override + public void beforeView(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException { + + } + + @Override + public void beforeDelete(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException { + + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java new file mode 100644 index 0000000000..00ebe05c3a --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 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.clientregistration.policy.impl; + +import java.util.LinkedList; +import java.util.List; + +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ConfigurationValidationHelper; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.services.clientregistration.policy.AbstractClientRegistrationPolicyFactory; +import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; + +/** + * @author Marek Posolda + */ +public class MaxClientsClientRegistrationPolicyFactory extends AbstractClientRegistrationPolicyFactory { + + public static final String MAX_CLIENTS = "max-clients"; + public static final ProviderConfigProperty MAX_CLIENTS_PROPERTY = new ProviderConfigProperty(); + + public static final int DEFAULT_MAX_CLIENTS = 200; + + private static List configProperties = new LinkedList<>(); + + static { + MAX_CLIENTS_PROPERTY.setName(MAX_CLIENTS); + MAX_CLIENTS_PROPERTY.setLabel("max-clients.label"); + MAX_CLIENTS_PROPERTY.setHelpText("max-clients.tooltip"); + MAX_CLIENTS_PROPERTY.setType(ProviderConfigProperty.STRING_TYPE); + MAX_CLIENTS_PROPERTY.setDefaultValue(String.valueOf(DEFAULT_MAX_CLIENTS)); + configProperties.add(MAX_CLIENTS_PROPERTY); + } + + public static final String PROVIDER_ID = "max-clients"; + + @Override + public ClientRegistrationPolicy create(KeycloakSession session, ComponentModel model) { + return new MaxClientsClientRegistrationPolicy(session, model); + } + + @Override + public String getHelpText() { + return "When present, then it won't be allowed to register new client if count of existing clients in realm is same or bigger than configured limit"; + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + ConfigurationValidationHelper.check(config) + .checkInt(MAX_CLIENTS_PROPERTY, true); + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java index 39f152c2c4..d45e26ea4e 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -28,6 +28,7 @@ import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.ErrorResponseException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -43,9 +44,14 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; + +import java.text.MessageFormat; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; /** * @author Bill Burke @@ -116,7 +122,7 @@ public class ComponentResource { adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); } catch (ComponentValidationException e) { - return ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST); + return localizedErrorResponse(e); } } @@ -147,7 +153,7 @@ public class ComponentResource { realm.updateComponent(model); return Response.noContent().build(); } catch (ComponentValidationException e) { - return ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST); + return localizedErrorResponse(e); } } @@ -164,6 +170,22 @@ public class ComponentResource { } + private Response localizedErrorResponse(ComponentValidationException cve) { + Properties messages = AdminRoot.getMessages(session, realm, "admin-messages", auth.getAuth().getToken().getLocale()); + Object[] localizedParameters = Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> { + + if (parameter instanceof String) { + String paramStr = (String) parameter; + return messages.getProperty(paramStr, paramStr); + } else { + return parameter; + } + + }).toArray(); + + String message = MessageFormat.format(messages.getProperty(cve.getMessage(), cve.getMessage()), localizedParameters); + return ErrorResponse.error(message, Response.Status.BAD_REQUEST); + } } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory index 652501ab49..78a8cc926e 100644 --- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory @@ -19,4 +19,6 @@ org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrati org.keycloak.services.clientregistration.policy.impl.ConsentRequiredClientRegistrationPolicyFactory org.keycloak.services.clientregistration.policy.impl.ProtocolMappersClientRegistrationPolicyFactory org.keycloak.services.clientregistration.policy.impl.ClientTemplatesClientRegistrationPolicyFactory -org.keycloak.services.clientregistration.policy.impl.ScopeClientRegistrationPolicyFactory \ No newline at end of file +org.keycloak.services.clientregistration.policy.impl.ScopeClientRegistrationPolicyFactory +org.keycloak.services.clientregistration.policy.impl.ClientDisabledClientRegistrationPolicyFactory +org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicyFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java index 2a4ef8b7e6..d5055faea7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java @@ -51,7 +51,9 @@ import org.keycloak.services.clientregistration.RegistrationAccessToken; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager; import org.keycloak.services.clientregistration.policy.RegistrationAuth; +import org.keycloak.services.clientregistration.policy.impl.ClientDisabledClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.ClientTemplatesClientRegistrationPolicyFactory; +import org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.ProtocolMappersClientRegistrationPolicyFactory; import org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrationPolicyFactory; import org.keycloak.testsuite.Assert; @@ -269,6 +271,61 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe } + @Test + public void testClientDisabledPolicy() throws Exception { + setTrustedHost("localhost", getPolicyAnon()); + + // Assert new client is enabled + OIDCClientRepresentation client = create(); + String clientId = client.getClientId(); + ClientRepresentation clientRep = ApiUtil.findClientByClientId(realmResource(), clientId).toRepresentation(); + Assert.assertTrue(clientRep.isEnabled()); + + // Add client-disabled policy + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setName("Clients disabled"); + rep.setParentId(REALM_NAME); + rep.setProviderId(ClientDisabledClientRegistrationPolicyFactory.PROVIDER_ID); + rep.setProviderType(ClientRegistrationPolicy.class.getName()); + rep.setSubType(getPolicyAnon()); + realmResource().components().add(rep); + + // Assert new client is disabled + client = create(); + clientId = client.getClientId(); + clientRep = ApiUtil.findClientByClientId(realmResource(), clientId).toRepresentation(); + Assert.assertFalse(clientRep.isEnabled()); + + // Try enable client. Should fail + clientRep.setEnabled(true); + assertFail(ClientRegOp.UPDATE, clientRep, 403, "Not permitted to enable client"); + + // Try update disabled client. Should pass + clientRep.setEnabled(false); + reg.update(clientRep); + } + + + @Test + public void testMaxClientsPolicy() throws Exception { + setTrustedHost("localhost", getPolicyAnon()); + + int clientsCount = realmResource().clients().findAll().size(); + int newClientsLimit = clientsCount + 1; + + // Allow to create one more client to current limit + ComponentRepresentation maxClientsPolicyRep = findPolicyByProviderAndAuth(MaxClientsClientRegistrationPolicyFactory.PROVIDER_ID, getPolicyAnon()); + maxClientsPolicyRep.getConfig().putSingle(MaxClientsClientRegistrationPolicyFactory.MAX_CLIENTS, String.valueOf(newClientsLimit)); + realmResource().components().component(maxClientsPolicyRep.getId()).update(maxClientsPolicyRep); + + // I can register one new client + OIDCClientRepresentation client = create(); + + // I can't register more clients + assertOidcFail(ClientRegOp.CREATE, createRepOidc(), 403, "It's allowed to have max " + newClientsLimit + " clients per realm"); + } + + @Test public void testProviders() throws Exception { List reps = realmResource().clientRegistrationPolicy().getProviders(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTester.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTester.java new file mode 100644 index 0000000000..396842db5c --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTester.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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.testsuite.client; + +import java.util.Arrays; + +import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.HttpErrorException; +import org.keycloak.representations.idm.ClientRepresentation; + +/** + * @author Marek Posolda + */ +public class ClientRegistrationTester { + + public static void main(String[] args) throws ClientRegistrationException { + ClientRepresentation rep = createRep1(); + + ClientRegistration reg = ClientRegistration.create().url("http://localhost:8081/auth", "test").build(); + + try { + ClientRepresentation createdRep = reg.create(rep); + System.out.println("Created client: " + createdRep.getClientId()); + } catch (ClientRegistrationException ex) { + HttpErrorException httpEx = (HttpErrorException) ex.getCause(); + System.err.println("HttpException when registering client. Status=" + httpEx.getStatusLine().getStatusCode() + ", Details=" + httpEx.getErrorResponse()); + } + } + + + private static ClientRepresentation createRep1() { + ClientRepresentation rep = new ClientRepresentation(); + rep.setRedirectUris(Arrays.asList("http://localhost:8080/app")); + rep.setDefaultRoles(new String[] { "foo-role" }); + return rep; + } + +} diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index ef8ef749c5..37055f2007 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -543,6 +543,7 @@ initial-access-token.confirm.text=Please copy and paste the initial access token no-initial-access-available=No Initial Access Tokens available client-reg-policies=Client Registration Policies +client-reg-policy.name.tooltip=Display Name of the policy anonymous-policies=Anonymous Access Policies anonymous-policies.tooltip=Those Policies are used when Client Registration Service is invoked by unauthenticated request. This means request doesn't contain Initial Access Token nor Bearer Token. auth-policies=Authenticated Access Policies @@ -561,6 +562,8 @@ consent-required-for-all-mappers.label=Consent Required For Mappers consent-required-for-all-mappers.tooltip=If on, then all newly registered protocol mappers will automatically have consentRequired switch on. This means that user will need to approve consent screen. NOTE: Consent screen is shown just if client has consentRequired switch on. So it's usually good to use this switch together with consent-required policy. allowed-client-templates.label=Allowed Client Templates allowed-client-templates.tooltip=Whitelist of the client templates, which can be used on newly registered client. Attempt to register client with some client template, which is not whitelisted, will be rejected. By default, the whitelist is empty, so there are not any client templates are allowed. +max-clients.label=Max Clients Per Realm +max-clients.tooltip=It won't be allowed to register new client if count of existing clients in realm is same or bigger than configured limit. client-templates=Client Templates client-templates.tooltip=Client templates allow you to define common configuration that is shared between multiple clients From 4136d76b7e8f120f5f3ebd7405a7c41618700a2b Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 19 Oct 2016 13:28:52 +0200 Subject: [PATCH 2/3] Minor javadoc update --- server-spi/src/main/java/org/keycloak/models/UserModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java index 6bc80c3179..233c8a8614 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java @@ -57,7 +57,7 @@ public interface UserModel extends RoleMapperModel { void setEnabled(boolean enabled); /** - * Set single value of specified attribute. Remove all other existing values + * Set single value of specified attribute. Remove all other existing values of this attribute * * @param name * @param value From 3779bfb6b40b5bc338c8793277703647982d2550 Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 19 Oct 2016 15:12:22 +0200 Subject: [PATCH 3/3] KEYCLOAK-3666 client registration policies - polishing --- ...RegistrationTrustedHostRepresentation.java | 60 ------ ...ClientRegistrationTrustedHostResource.java | 63 ------ .../ClientRegistrationTrustedHostAdapter.java | 87 --------- .../InfinispanUserSessionProvider.java | 66 ------- .../ClientRegistrationTrustedHostEntity.java | 54 ------ ...lientRegistrationTrustedHostPredicate.java | 58 ------ .../java/org/keycloak/events/Details.java | 2 + .../keycloak/events/admin/ResourceType.java | 5 - .../ClientRegistrationTrustedHostModel.java | 36 ---- .../keycloak/models/UserSessionProvider.java | 5 - .../ConfigurationValidationHelper.java | 2 +- .../org/keycloak/services/ServicesLogger.java | 19 +- .../AbstractClientRegistrationProvider.java | 5 + ...nstallationClientRegistrationProvider.java | 5 + .../ClientRegistrationHostUtils.java | 67 ------- .../ClientRegistrationProvider.java | 2 + .../ClientRegistrationPolicyManager.java | 25 ++- ...ientTemplatesClientRegistrationPolicy.java | 2 - ...otocolMappersClientRegistrationPolicy.java | 12 +- .../TrustedHostClientRegistrationPolicy.java | 17 +- ...ClientRegistrationTrustedHostResource.java | 180 ------------------ .../resources/admin/ComponentResource.java | 2 +- .../testsuite/admin/ComponentsTest.java | 4 +- .../cli/registration/AbstractCliTest.java | 3 - .../client/OIDCClientRegistrationTest.java | 1 - .../testsuite/keys/RsaKeyProviderTest.java | 8 +- .../testsuite/util/AdminEventPaths.java | 9 - .../base/src/test/resources/log4j.properties | 2 +- 28 files changed, 66 insertions(+), 735 deletions(-) delete mode 100644 core/src/main/java/org/keycloak/representations/idm/ClientRegistrationTrustedHostRepresentation.java delete mode 100644 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientRegistrationTrustedHostResource.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientRegistrationTrustedHostAdapter.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientRegistrationTrustedHostEntity.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientRegistrationTrustedHostPredicate.java delete mode 100644 server-spi/src/main/java/org/keycloak/models/ClientRegistrationTrustedHostModel.java delete mode 100644 services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationHostUtils.java delete mode 100644 services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationTrustedHostResource.java diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRegistrationTrustedHostRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRegistrationTrustedHostRepresentation.java deleted file mode 100644 index 1df7ab44b2..0000000000 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRegistrationTrustedHostRepresentation.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2016 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.representations.idm; - -/** - * @author Marek Posolda - */ -public class ClientRegistrationTrustedHostRepresentation { - - String hostName; - Integer count; - Integer remainingCount; - - public static ClientRegistrationTrustedHostRepresentation create(String hostName, int count, int remainingCount) { - ClientRegistrationTrustedHostRepresentation rep = new ClientRegistrationTrustedHostRepresentation(); - rep.setHostName(hostName); - rep.setCount(count); - rep.setRemainingCount(remainingCount); - return rep; - } - - public String getHostName() { - return hostName; - } - - public void setHostName(String hostName) { - this.hostName = hostName; - } - - public Integer getCount() { - return count; - } - - public void setCount(Integer count) { - this.count = count; - } - - public Integer getRemainingCount() { - return remainingCount; - } - - public void setRemainingCount(Integer remainingCount) { - this.remainingCount = remainingCount; - } -} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientRegistrationTrustedHostResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientRegistrationTrustedHostResource.java deleted file mode 100644 index 29e3864ffb..0000000000 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientRegistrationTrustedHostResource.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2016 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.admin.client.resource; - -import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.List; - -/** - * @author Marek Posolda - */ -public interface ClientRegistrationTrustedHostResource { - - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - Response create(ClientRegistrationTrustedHostRepresentation config); - - @PUT - @Path("{hostname}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - Response update(final @PathParam("hostname") String hostName, ClientRegistrationTrustedHostRepresentation config); - - @GET - @Path("{hostname}") - @Produces(MediaType.APPLICATION_JSON) - ClientRegistrationTrustedHostRepresentation get(final @PathParam("hostname") String hostName); - - @GET - @Produces(MediaType.APPLICATION_JSON) - List list(); - - @DELETE - @Path("{hostname}") - void delete(final @PathParam("hostname") String hostName); - -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientRegistrationTrustedHostAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientRegistrationTrustedHostAdapter.java deleted file mode 100644 index 34e68cc9f1..0000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientRegistrationTrustedHostAdapter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2016 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.models.sessions.infinispan; - -import org.infinispan.Cache; -import org.keycloak.models.ClientRegistrationTrustedHostModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.sessions.infinispan.entities.ClientRegistrationTrustedHostEntity; -import org.keycloak.models.sessions.infinispan.entities.SessionEntity; - -/** - * @author Marek Posolda - */ -public class ClientRegistrationTrustedHostAdapter implements ClientRegistrationTrustedHostModel { - - private final KeycloakSession session; - private final InfinispanUserSessionProvider provider; - private final Cache cache; - private final RealmModel realm; - private final ClientRegistrationTrustedHostEntity entity; - - public ClientRegistrationTrustedHostAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache cache, RealmModel realm, ClientRegistrationTrustedHostEntity entity) { - this.session = session; - this.provider = provider; - this.cache = cache; - this.realm = realm; - this.entity = entity; - } - - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public String getHostName() { - return entity.getHostName(); - } - - @Override - public int getCount() { - return entity.getCount(); - } - - @Override - public void setCount(int count) { - entity.setCount(count); - update(); - } - - @Override - public int getRemainingCount() { - return entity.getRemainingCount(); - } - - @Override - public void setRemainingCount(int remainingCount) { - entity.setRemainingCount(remainingCount); - update(); - } - - @Override - public void decreaseRemainingCount() { - entity.setRemainingCount(entity.getRemainingCount() - 1); - update(); - } - - void update() { - provider.getTx().replace(cache, entity.getId(), entity); - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index f6d1640d2d..44419cd8da 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -23,11 +23,9 @@ import org.jboss.logging.Logger; import org.keycloak.common.util.Time; import org.keycloak.models.ClientInitialAccessModel; import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientRegistrationTrustedHostModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; -import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserLoginFailureModel; import org.keycloak.models.UserModel; @@ -35,14 +33,12 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionProvider; import org.keycloak.models.session.UserSessionPersisterProvider; import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity; -import org.keycloak.models.sessions.infinispan.entities.ClientRegistrationTrustedHostEntity; import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity; import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey; import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate; -import org.keycloak.models.sessions.infinispan.stream.ClientRegistrationTrustedHostPredicate; import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate; import org.keycloak.models.sessions.infinispan.stream.Comparators; import org.keycloak.models.sessions.infinispan.stream.Mappers; @@ -540,12 +536,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null; } - ClientRegistrationTrustedHostAdapter wrap(RealmModel realm, ClientRegistrationTrustedHostEntity entity) { - Cache cache = getCache(false); - return entity != null ? new ClientRegistrationTrustedHostAdapter(session, this, cache, realm, entity) : null; - } - - UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) { return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null; } @@ -737,62 +727,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { return list; } - @Override - public ClientRegistrationTrustedHostModel createClientRegistrationTrustedHostModel(RealmModel realm, String hostName, int count) { - if (getClientRegistrationTrustedHostModel(realm, hostName) != null) { - throw new ModelDuplicateException("Client registration already exists for this realm and hostName"); - } - - String id = computeClientRegistrationTrustedHostEntityId(realm, hostName); - - ClientRegistrationTrustedHostEntity entity = new ClientRegistrationTrustedHostEntity(); - entity.setId(id); - entity.setHostName(hostName); - entity.setRealm(realm.getId()); - entity.setCount(count); - entity.setRemainingCount(count); - - tx.put(sessionCache, id, entity); - - return wrap(realm, entity); - } - - @Override - public ClientRegistrationTrustedHostModel getClientRegistrationTrustedHostModel(RealmModel realm, String hostName) { - String id = computeClientRegistrationTrustedHostEntityId(realm, hostName); - - Cache cache = getCache(false); - ClientRegistrationTrustedHostEntity entity = (ClientRegistrationTrustedHostEntity) cache.get(id); - - // If created in this transaction - if (entity == null) { - entity = (ClientRegistrationTrustedHostEntity) tx.get(cache, id); - } - - return wrap(realm, entity); - } - - @Override - public void removeClientRegistrationTrustedHostModel(RealmModel realm, String hostName) { - String id = computeClientRegistrationTrustedHostEntityId(realm, hostName); - tx.remove(getCache(false), id); - } - - @Override - public List listClientRegistrationTrustedHosts(RealmModel realm) { - Iterator> itr = sessionCache.entrySet().stream().filter(ClientRegistrationTrustedHostPredicate.create(realm.getId())).iterator(); - List list = new LinkedList<>(); - while (itr.hasNext()) { - list.add(wrap(realm, (ClientRegistrationTrustedHostEntity) itr.next().getValue())); - } - return list; - } - - private static final String CLIENT_REG_TRUSTED_HOST_ID_PREFIX = "reg:::"; - - private String computeClientRegistrationTrustedHostEntityId(RealmModel realm, String hostName) { - return CLIENT_REG_TRUSTED_HOST_ID_PREFIX + realm.getId() + ":::" + hostName; - } class InfinispanKeycloakTransaction implements KeycloakTransaction { diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientRegistrationTrustedHostEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientRegistrationTrustedHostEntity.java deleted file mode 100644 index 53b1ec868c..0000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientRegistrationTrustedHostEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2016 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.models.sessions.infinispan.entities; - -/** - * @author Marek Posolda - */ -public class ClientRegistrationTrustedHostEntity extends SessionEntity { - - private String hostName; - - private int count; - - private int remainingCount; - - public String getHostName() { - return hostName; - } - - public void setHostName(String hostName) { - this.hostName = hostName; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public int getRemainingCount() { - return remainingCount; - } - - public void setRemainingCount(int remainingCount) { - this.remainingCount = remainingCount; - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientRegistrationTrustedHostPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientRegistrationTrustedHostPredicate.java deleted file mode 100644 index 7565a89073..0000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientRegistrationTrustedHostPredicate.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016 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.models.sessions.infinispan.stream; - -import org.keycloak.models.sessions.infinispan.entities.ClientRegistrationTrustedHostEntity; -import org.keycloak.models.sessions.infinispan.entities.SessionEntity; - -import java.io.Serializable; -import java.util.Map; -import java.util.function.Predicate; - -/** - * @author Marek Posolda - */ -public class ClientRegistrationTrustedHostPredicate implements Predicate>, Serializable { - - public static ClientRegistrationTrustedHostPredicate create(String realm) { - return new ClientRegistrationTrustedHostPredicate(realm); - } - - private ClientRegistrationTrustedHostPredicate(String realm) { - this.realm = realm; - } - - private String realm; - - - @Override - public boolean test(Map.Entry entry) { - SessionEntity e = entry.getValue(); - - if (!realm.equals(e.getRealm())) { - return false; - } - - if (!(e instanceof ClientRegistrationTrustedHostEntity)) { - return false; - } - - return true; - } - -} diff --git a/server-spi/src/main/java/org/keycloak/events/Details.java b/server-spi/src/main/java/org/keycloak/events/Details.java index 772eaa794f..0ef227dabc 100755 --- a/server-spi/src/main/java/org/keycloak/events/Details.java +++ b/server-spi/src/main/java/org/keycloak/events/Details.java @@ -61,4 +61,6 @@ public interface Details { String SIGNATURE_REQUIRED = "signature_required"; String SIGNATURE_ALGORITHM = "signature_algorithm"; + String CLIENT_REGISTRATION_POLICY = "client_registration_policy"; + } diff --git a/server-spi/src/main/java/org/keycloak/events/admin/ResourceType.java b/server-spi/src/main/java/org/keycloak/events/admin/ResourceType.java index 8b9c27c1ad..045258c08b 100644 --- a/server-spi/src/main/java/org/keycloak/events/admin/ResourceType.java +++ b/server-spi/src/main/java/org/keycloak/events/admin/ResourceType.java @@ -128,11 +128,6 @@ public enum ResourceType { */ , CLIENT_INITIAL_ACCESS_MODEL - /** - * - */ - , CLIENT_REGISTRATION_TRUSTED_HOST_MODEL - /** * */ diff --git a/server-spi/src/main/java/org/keycloak/models/ClientRegistrationTrustedHostModel.java b/server-spi/src/main/java/org/keycloak/models/ClientRegistrationTrustedHostModel.java deleted file mode 100644 index 30b2e6fa2e..0000000000 --- a/server-spi/src/main/java/org/keycloak/models/ClientRegistrationTrustedHostModel.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2016 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.models; - -/** - * @author Marek Posolda - */ -public interface ClientRegistrationTrustedHostModel { - - RealmModel getRealm(); - - String getHostName(); - - int getCount(); - void setCount(int count); - - int getRemainingCount(); - void setRemainingCount(int remainingCount); - void decreaseRemainingCount(); - -} diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java index 27ce108734..585558c102 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java @@ -82,11 +82,6 @@ public interface UserSessionProvider extends Provider { void removeClientInitialAccessModel(RealmModel realm, String id); List listClientInitialAccess(RealmModel realm); - ClientRegistrationTrustedHostModel createClientRegistrationTrustedHostModel(RealmModel realm, String hostName, int count); - ClientRegistrationTrustedHostModel getClientRegistrationTrustedHostModel(RealmModel realm, String hostName); - void removeClientRegistrationTrustedHostModel(RealmModel realm, String hostName); - List listClientRegistrationTrustedHosts(RealmModel realm); - void close(); } diff --git a/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java b/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java index 9a2eea895b..047c1bd790 100644 --- a/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java +++ b/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java @@ -113,7 +113,7 @@ public class ConfigurationValidationHelper { String val = model.getConfig().getFirst(key); if (val != null && !(val.equals("true") || val.equals("false"))) { - throw new ComponentValidationException("''{0}'' should be 'true' or 'false'", label); + throw new ComponentValidationException("''{0}'' should be ''true'' or ''false''", label); } return this; diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java index b0e0ccaf9c..bd239a6e51 100644 --- a/services/src/main/java/org/keycloak/services/ServicesLogger.java +++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java @@ -434,8 +434,21 @@ public interface ServicesLogger extends BasicLogger { @Message(id=97, value="Invalid request") void invalidRequest(@Cause Throwable t); - @LogMessage(level = ERROR) - @Message(id=98, value="Failed to get redirect uris from sector identifier URI: %s") - void failedToGetRedirectUrisFromSectorIdentifierUri(@Cause Throwable t, String sectorIdentifierUri); + + @LogMessage(level = WARN) + @Message(id=99, value="Operation '%s' rejected. %s") + void clientRegistrationRequestRejected(String opDescription, String detailedMessage); + + @LogMessage(level = WARN) + @Message(id=100, value= "ProtocolMapper '%s' of type '%s' not allowed") + void clientRegistrationMapperNotAllowed(String mapperName, String mapperType); + + @LogMessage(level = WARN) + @Message(id=101, value= "Failed to verify remote host : %s") + void failedToVerifyRemoteHost(String hostname); + + @LogMessage(level = WARN) + @Message(id=102, value= "URL '%s' doesn't match any trustedHost or trustedDomain") + void urlDoesntMatch(String url); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java index 45f0430c93..2974b6cb3e 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java @@ -170,6 +170,11 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist this.event = event; } + @Override + public EventBuilder getEvent() { + return event; + } + @Override public void close() { } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java index e06ad8b225..54af568492 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java @@ -75,6 +75,11 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi this.event = event; } + @Override + public EventBuilder getEvent() { + return event; + } + @Override public void close() { } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationHostUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationHostUtils.java deleted file mode 100644 index c8986074c4..0000000000 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationHostUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2016 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.clientregistration; - -import org.jboss.logging.Logger; -import org.keycloak.models.ClientRegistrationTrustedHostModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.List; - -/** - * @author Marek Posolda - */ -public class ClientRegistrationHostUtils { - - private static final Logger logger = Logger.getLogger(ClientRegistrationHostUtils.class); - - /** - * @return null if host from request is not trusted. Otherwise return trusted host model - * - * TODO: Remove - */ - public static ClientRegistrationTrustedHostModel getTrustedHost(String hostAddress, KeycloakSession session, RealmModel realm) { - logger.debugf("Verifying remote host : %s", hostAddress); - - List trustedHosts = session.sessions().listClientRegistrationTrustedHosts(realm); - - for (ClientRegistrationTrustedHostModel realmTrustedHost : trustedHosts) { - try { - if (realmTrustedHost.getRemainingCount() <= 0) { - continue; - } - - String realmHostIPAddress = InetAddress.getByName(realmTrustedHost.getHostName()).getHostAddress(); - logger.debugf("Trying host '%s' of address '%s'", realmTrustedHost.getHostName(), realmHostIPAddress); - if (realmHostIPAddress.equals(hostAddress)) { - logger.debugf("Successfully verified host : %s", realmTrustedHost.getHostName()); - return realmTrustedHost; - } - } catch (UnknownHostException uhe) { - logger.debugf("Unknown host from realm configuration: %s", realmTrustedHost.getHostName()); - } - } - - logger.debugf("Failed to verify remote host : %s", hostAddress); - return null; - } - -} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java index f4660fa5f8..a2359342ca 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java @@ -31,4 +31,6 @@ public interface ClientRegistrationProvider extends Provider { void setEvent(EventBuilder event); + EventBuilder getEvent(); + } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/ClientRegistrationPolicyManager.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/ClientRegistrationPolicyManager.java index c2e6092639..2679fa1e22 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/ClientRegistrationPolicyManager.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/ClientRegistrationPolicyManager.java @@ -18,13 +18,16 @@ package org.keycloak.services.clientregistration.policy; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.component.ComponentModel; +import org.keycloak.events.Details; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.services.ServicesLogger; import org.keycloak.services.clientregistration.ClientRegistrationContext; import org.keycloak.services.clientregistration.ClientRegistrationProvider; @@ -36,7 +39,7 @@ public class ClientRegistrationPolicyManager { private static final Logger logger = Logger.getLogger(ClientRegistrationPolicyManager.class); public static void triggerBeforeRegister(ClientRegistrationContext context, RegistrationAuth authType) throws ClientRegistrationPolicyException { - triggerPolicies(context.getSession(), authType, "before register client", (ClientRegistrationPolicy policy) -> { + triggerPolicies(context.getSession(), context.getProvider(), authType, "before register client", (ClientRegistrationPolicy policy) -> { policy.beforeRegister(context); @@ -46,7 +49,7 @@ public class ClientRegistrationPolicyManager { public static void triggerAfterRegister(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) { try { - triggerPolicies(context.getSession(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { + triggerPolicies(context.getSession(), context.getProvider(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { policy.afterRegister(context, client); @@ -58,7 +61,7 @@ public class ClientRegistrationPolicyManager { public static void triggerBeforeUpdate(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) throws ClientRegistrationPolicyException { - triggerPolicies(context.getSession(), authType, "before update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { + triggerPolicies(context.getSession(), context.getProvider(), authType, "before update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { policy.beforeUpdate(context, client); @@ -67,7 +70,7 @@ public class ClientRegistrationPolicyManager { public static void triggerAfterUpdate(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) { try { - triggerPolicies(context.getSession(), authType, "after update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { + triggerPolicies(context.getSession(), context.getProvider(), authType, "after update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { policy.afterUpdate(context, client); @@ -78,7 +81,7 @@ public class ClientRegistrationPolicyManager { } public static void triggerBeforeView(KeycloakSession session, ClientRegistrationProvider provider, RegistrationAuth authType, ClientModel client) throws ClientRegistrationPolicyException { - triggerPolicies(session, authType, "before view client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { + triggerPolicies(session, provider, authType, "before view client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { policy.beforeView(provider, client); @@ -86,7 +89,7 @@ public class ClientRegistrationPolicyManager { } public static void triggerBeforeRemove(KeycloakSession session, ClientRegistrationProvider provider, RegistrationAuth authType, ClientModel client) throws ClientRegistrationPolicyException { - triggerPolicies(session, authType, "before delete client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { + triggerPolicies(session, provider, authType, "before delete client " + client.getClientId(), (ClientRegistrationPolicy policy) -> { policy.beforeDelete(provider, client); @@ -95,7 +98,7 @@ public class ClientRegistrationPolicyManager { - private static void triggerPolicies(KeycloakSession session, RegistrationAuth authType, String opDescription, ClientRegOperation op) throws ClientRegistrationPolicyException { + private static void triggerPolicies(KeycloakSession session, ClientRegistrationProvider provider, RegistrationAuth authType, String opDescription, ClientRegOperation op) throws ClientRegistrationPolicyException { RealmModel realm = session.getContext().getRealm(); String policyTypeKey = getComponentTypeKey(authType); @@ -113,14 +116,16 @@ public class ClientRegistrationPolicyManager { throw new ClientRegistrationPolicyException("Policy of type '" + policyModel.getProviderId() + "' not found"); } - // TODO: trace - logger.infof("Running policy '%s' %s", policyModel.getName(), opDescription); + if (logger.isTraceEnabled()) { + logger.tracef("Running policy '%s' %s", policyModel.getName(), opDescription); + } try { op.run(policy); } catch (ClientRegistrationPolicyException crpe) { + provider.getEvent().detail(Details.CLIENT_REGISTRATION_POLICY, policyModel.getName()); crpe.setPolicyModel(policyModel); - logger.warnf("Operation '%s' rejected. %s", opDescription, crpe.getMessage()); + ServicesLogger.LOGGER.clientRegistrationRequestRejected(opDescription, crpe.getMessage()); throw crpe; } } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientTemplatesClientRegistrationPolicy.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientTemplatesClientRegistrationPolicy.java index 41d7ba7786..508dad490b 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientTemplatesClientRegistrationPolicy.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ClientTemplatesClientRegistrationPolicy.java @@ -35,8 +35,6 @@ import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyE */ public class ClientTemplatesClientRegistrationPolicy implements ClientRegistrationPolicy { - private static final Logger logger = Logger.getLogger(ClientTemplatesClientRegistrationPolicy.class); - private final KeycloakSession session; private final ComponentModel componentModel; diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ProtocolMappersClientRegistrationPolicy.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ProtocolMappersClientRegistrationPolicy.java index 4bfdf3212b..f273def1f1 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ProtocolMappersClientRegistrationPolicy.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/ProtocolMappersClientRegistrationPolicy.java @@ -18,17 +18,15 @@ package org.keycloak.services.clientregistration.policy.impl; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.services.ServicesLogger; import org.keycloak.services.clientregistration.ClientRegistrationContext; import org.keycloak.services.clientregistration.ClientRegistrationProvider; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; @@ -66,7 +64,7 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati String mapperType = mapper.getProtocolMapper(); if (!allowedMapperProviders.contains(mapperType)) { - logger.warnf("ProtocolMapper '%s' of type '%s' not allowed", mapper.getName(), mapperType); + ServicesLogger.LOGGER.clientRegistrationMapperNotAllowed(mapper.getName(), mapperType); throw new ClientRegistrationPolicyException("ProtocolMapper type not allowed"); } } @@ -75,8 +73,7 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati protected void enableConsentRequiredForAll(ClientModel clientModel) { if (isConsentRequiredForMappers()) { - // TODO: Debug - logger.infof("Enable consentRequired for all protocol mappers of client %s", clientModel.getClientId()); + logger.debugf("Enable consentRequired for all protocol mappers of client %s", clientModel.getClientId()); Set mappers = clientModel.getProtocolMappers(); @@ -105,8 +102,7 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati }).forEach((ProtocolMapperModel mapperToRemove) -> { - // TODO: debug - logger.infof("Removing builtin mapper '%s' of type '%s' as type is not permitted", mapperToRemove.getName(), mapperToRemove.getProtocolMapper()); + logger.debugf("Removing builtin mapper '%s' of type '%s' as type is not permitted", mapperToRemove.getName(), mapperToRemove.getProtocolMapper()); clientModel.removeProtocolMapper(mapperToRemove); }); diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java index d5359d8ace..84f2662153 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicy.java @@ -32,6 +32,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.services.ServicesLogger; import org.keycloak.services.clientregistration.ClientRegistrationContext; import org.keycloak.services.clientregistration.ClientRegistrationProvider; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; @@ -95,8 +96,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo String hostAddress = session.getContext().getConnection().getRemoteAddr(); - // TODO: debug - logger.infof("Verifying remote host : %s", hostAddress); + logger.debugf("Verifying remote host : %s", hostAddress); List trustedHosts = getTrustedHosts(); List trustedDomains = getTrustedDomains(); @@ -113,7 +113,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo return; } - logger.warnf("Failed to verify remote host : %s", hostAddress); + ServicesLogger.LOGGER.failedToVerifyRemoteHost(hostAddress); throw new ClientRegistrationPolicyException("Host not trusted."); } @@ -154,7 +154,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo return confHostName; } } catch (UnknownHostException uhe) { - logger.warnf("Unknown host from realm configuration: %s", confHostName); + logger.debugf(uhe, "Unknown host from realm configuration: %s", confHostName); } } @@ -167,8 +167,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo try { String hostname = InetAddress.getByName(hostAddress).getHostName(); - // TODO: Debug - logger.infof("Trying verify request from address '%s' of host '%s' by domains", hostAddress, hostname); + logger.debugf("Trying verify request from address '%s' of host '%s' by domains", hostAddress, hostname); for (String confDomain : trustedDomains) { if (hostname.endsWith(confDomain)) { @@ -177,7 +176,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo } } } catch (UnknownHostException uhe) { - logger.warnf("Request of address '%s' came from unknown host. Skip verification by domains", hostAddress); + logger.debugf(uhe, "Request of address '%s' came from unknown host. Skip verification by domains", hostAddress); } } @@ -237,11 +236,11 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo } } } catch (MalformedURLException mfe) { - logger.warnf("URL '%s' is malformed", url); + logger.debugf(mfe, "URL '%s' is malformed", url); throw new ClientRegistrationPolicyException("URL is malformed"); } - logger.warnf("URL '%s' doesn't match any trustedHost or trustedDomain", url); + ServicesLogger.LOGGER.urlDoesntMatch(url); throw new ClientRegistrationPolicyException("URL doesn't match any trusted host or trusted domain"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationTrustedHostResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationTrustedHostResource.java deleted file mode 100644 index b1dfbfd780..0000000000 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationTrustedHostResource.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2016 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.resources.admin; - -import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.events.admin.OperationType; -import org.keycloak.events.admin.ResourceType; -import org.keycloak.models.ClientRegistrationTrustedHostModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation; -import org.keycloak.services.ErrorResponse; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Marek Posolda - */ -public class ClientRegistrationTrustedHostResource { - - private final RealmAuth auth; - private final RealmModel realm; - private final AdminEventBuilder adminEvent; - - @Context - protected KeycloakSession session; - - @Context - protected UriInfo uriInfo; - - public ClientRegistrationTrustedHostResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { - this.auth = auth; - this.realm = realm; - this.adminEvent = adminEvent.resource(ResourceType.CLIENT_REGISTRATION_TRUSTED_HOST_MODEL); - - auth.init(RealmAuth.Resource.CLIENT); - } - - /** - * Create a new initial access token. - * - * @param config - * @return - */ - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Response create(ClientRegistrationTrustedHostRepresentation config) { - auth.requireManage(); - - if (config.getHostName() == null) { - return ErrorResponse.error("hostName not provided in config", Response.Status.BAD_REQUEST); - } - - int count = config.getCount() != null ? config.getCount() : 1; - - try { - ClientRegistrationTrustedHostModel clientRegTrustedHostModel = session.sessions().createClientRegistrationTrustedHostModel(realm, config.getHostName(), count); - - adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientRegTrustedHostModel.getHostName()).representation(config).success(); - - return Response.created(uriInfo.getAbsolutePathBuilder().path(clientRegTrustedHostModel.getHostName()).build()).build(); - } catch (ModelDuplicateException mde) { - return ErrorResponse.exists(mde.getMessage()); - } - } - - /** - * Update a new initial access token. - * - * @param config - * @return - */ - @PUT - @Path("{hostname}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Response update(final @PathParam("hostname") String hostName, ClientRegistrationTrustedHostRepresentation config) { - auth.requireManage(); - - if (config.getHostName() == null || !hostName.equals(config.getHostName())) { - return ErrorResponse.error("hostName not provided in config or not compatible", Response.Status.BAD_REQUEST); - } - - if (config.getCount() == null) { - return ErrorResponse.error("count needs to be available", Response.Status.BAD_REQUEST); - } - - if (config.getRemainingCount() != null && config.getRemainingCount() > config.getCount()) { - return ErrorResponse.error("remainingCount can't be bigger than count", Response.Status.BAD_REQUEST); - } - - ClientRegistrationTrustedHostModel hostModel = session.sessions().getClientRegistrationTrustedHostModel(realm, config.getHostName()); - if (hostModel == null) { - return ErrorResponse.error("hostName record not found", Response.Status.NOT_FOUND); - } - - hostModel.setCount(config.getCount()); - hostModel.setRemainingCount(config.getRemainingCount()); - - adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(config).success(); - return Response.noContent().build(); - } - - /** - * Get an initial access token. - * - * @param hostName - * @return - */ - @GET - @Path("{hostname}") - @Produces(MediaType.APPLICATION_JSON) - public ClientRegistrationTrustedHostRepresentation getConfig(final @PathParam("hostname") String hostName) { - auth.requireView(); - - ClientRegistrationTrustedHostModel hostModel = session.sessions().getClientRegistrationTrustedHostModel(realm, hostName); - if (hostModel == null) { - throw new NotFoundException("hostName record not found"); - } - - return wrap(hostModel); - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - public List list() { - auth.requireView(); - - List models = session.sessions().listClientRegistrationTrustedHosts(realm); - List reps = new LinkedList<>(); - for (ClientRegistrationTrustedHostModel m : models) { - ClientRegistrationTrustedHostRepresentation r = wrap(m); - reps.add(r); - } - return reps; - } - - @DELETE - @Path("{hostname}") - public void delete(final @PathParam("hostname") String hostName) { - auth.requireManage(); - - session.sessions().removeClientRegistrationTrustedHostModel(realm, hostName); - adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); - } - - private ClientRegistrationTrustedHostRepresentation wrap(ClientRegistrationTrustedHostModel model) { - return ClientRegistrationTrustedHostRepresentation.create(model.getHostName(), model.getCount(), model.getRemainingCount()); - } -} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java index d45e26ea4e..5a0e817af1 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -173,7 +173,7 @@ public class ComponentResource { private Response localizedErrorResponse(ComponentValidationException cve) { Properties messages = AdminRoot.getMessages(session, realm, "admin-messages", auth.getAuth().getToken().getLocale()); - Object[] localizedParameters = Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> { + Object[] localizedParameters = cve.getParameters()==null ? null : Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> { if (parameter instanceof String) { String paramStr = (String) parameter; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java index ab934fe8d3..dba19dd538 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java @@ -65,7 +65,7 @@ public class ComponentsTest extends AbstractAdminTest { try { createComponent(rep); } catch (WebApplicationException e) { - assertErrror(e.getResponse(), "Required is required"); + assertErrror(e.getResponse(), "'Required' is required"); } rep.getConfig().putSingle("required", "Required"); @@ -75,7 +75,7 @@ public class ComponentsTest extends AbstractAdminTest { try { createComponent(rep); } catch (WebApplicationException e) { - assertErrror(e.getResponse(), "Number should be a number"); + assertErrror(e.getResponse(), "'Number' should be a number"); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java index 01ef40480f..e44a3485fc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java @@ -3,7 +3,6 @@ package org.keycloak.testsuite.cli.registration; import org.junit.Assert; import org.junit.Before; import org.keycloak.admin.client.resource.ClientInitialAccessResource; -import org.keycloak.admin.client.resource.ClientRegistrationTrustedHostResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; @@ -12,7 +11,6 @@ import org.keycloak.client.registration.cli.config.FileConfigHandler; import org.keycloak.client.registration.cli.config.RealmConfigData; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; -import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -27,7 +25,6 @@ import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.util.JsonSerialization; -import javax.ws.rs.core.Response; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index f927851c1f..2912c3ce57 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -31,7 +31,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; -import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java index 65cda1a98e..2dc3506bfc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java @@ -144,7 +144,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid"); Response response = adminClient.realm("test").components().add(rep); - assertErrror(response, "Priority should be a number"); + assertErrror(response, "'Priority' should be a number"); } @Test @@ -156,7 +156,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid"); Response response = adminClient.realm("test").components().add(rep); - assertErrror(response, "Enabled should be 'true' or 'false'"); + assertErrror(response, "'Enabled' should be 'true' or 'false'"); } @Test @@ -168,7 +168,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid"); Response response = adminClient.realm("test").components().add(rep); - assertErrror(response, "Active should be 'true' or 'false'"); + assertErrror(response, "'Active' should be 'true' or 'false'"); } @Test @@ -178,7 +178,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); Response response = adminClient.realm("test").components().add(rep); - assertErrror(response, "Private RSA Key is required"); + assertErrror(response, "'Private RSA Key' is required"); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, "nonsense"); response = adminClient.realm("test").components().add(rep); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java index 5e5547f73e..74e0046999 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java @@ -316,15 +316,6 @@ public class AdminEventPaths { return uri.toString(); } - // CLIENT REGISTRATION TRUSTED HOSTS - - public static String clientRegistrationTrustedHostPath(String hostName) { - URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clientRegistrationTrustedHost") - .path(ClientInitialAccessResource.class, "delete") - .build(hostName); - return uri.toString(); - } - // GROUPS public static String groupsPath() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties index 91aa2a636a..5605c9d248 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties @@ -67,4 +67,4 @@ log4j.logger.org.apache.directory.server.core=warn # log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace # log4j.logger.org.keycloak.keys.infinispan=trace -log4j.logger.org.keycloak.services.clientregistration.policy=trace \ No newline at end of file +log4j.logger.org.keycloak.services.clientregistration.policy=debug \ No newline at end of file