KEYCLOAK-12190 Add validation for client root and base URLs
This commit is contained in:
parent
27f6f7bf40
commit
7545749632
19 changed files with 506 additions and 8 deletions
|
@ -58,6 +58,7 @@ public interface Errors {
|
|||
String INVALID_FORM = "invalid_form";
|
||||
String INVALID_CONFIG = "invalid_config";
|
||||
String EXPIRED_CODE = "expired_code";
|
||||
String INVALID_INPUT = "invalid_input";
|
||||
|
||||
String REGISTRATION_DISABLED = "registration_disabled";
|
||||
String RESET_CREDENTIAL_DISABLED = "reset_credential_disabled";
|
||||
|
|
|
@ -133,6 +133,9 @@ import org.keycloak.storage.UserStorageProvider;
|
|||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.validation.ClientValidationContext;
|
||||
import org.keycloak.validation.ClientValidationProvider;
|
||||
import org.keycloak.validation.ClientValidationUtil;
|
||||
|
||||
public class RepresentationToModel {
|
||||
|
||||
|
@ -1235,6 +1238,10 @@ public class RepresentationToModel {
|
|||
for (ClientRepresentation resourceRep : rep.getClients()) {
|
||||
ClientModel app = createClient(session, realm, resourceRep, false, mappedFlows);
|
||||
appMap.put(app.getClientId(), app);
|
||||
|
||||
ClientValidationUtil.validate(session, app, false, c -> {
|
||||
throw new RuntimeException("Invalid client " + app.getClientId() + ": " + c.getError());
|
||||
});
|
||||
}
|
||||
return appMap;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public interface ClientValidationContext {
|
||||
|
||||
enum Event {
|
||||
CREATE,
|
||||
UPDATE
|
||||
}
|
||||
|
||||
Event getEvent();
|
||||
|
||||
KeycloakSession getSession();
|
||||
|
||||
ClientModel getClient();
|
||||
|
||||
String getError();
|
||||
|
||||
ClientValidationContext invalid(String error);
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface ClientValidationProvider extends Provider {
|
||||
|
||||
void validate(ClientValidationContext context);
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ClientValidationProviderFactory extends ProviderFactory<ClientValidationProvider> {
|
||||
|
||||
@Override
|
||||
default void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class ClientValidationSPI implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clientValidation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return ClientValidationProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return ClientValidationProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
|
||||
public class ClientValidationUtil {
|
||||
|
||||
public static void validate(KeycloakSession session, ClientModel client, boolean create, ErrorHandler errorHandler) throws BadRequestException {
|
||||
ClientValidationProvider provider = session.getProvider(ClientValidationProvider.class);
|
||||
if (provider != null) {
|
||||
DefaultClientValidationContext context = new DefaultClientValidationContext(create ? ClientValidationContext.Event.CREATE : ClientValidationContext.Event.UPDATE, session, client);
|
||||
provider.validate(context);
|
||||
|
||||
if (!context.isValid()) {
|
||||
errorHandler.onError(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ErrorHandler {
|
||||
|
||||
void onError(ClientValidationContext context);
|
||||
|
||||
}
|
||||
|
||||
private static class DefaultClientValidationContext implements ClientValidationContext {
|
||||
|
||||
private Event event;
|
||||
private KeycloakSession session;
|
||||
private ClientModel client;
|
||||
|
||||
private String error;
|
||||
|
||||
public DefaultClientValidationContext(Event event, KeycloakSession session, ClientModel client) {
|
||||
this.event = event;
|
||||
this.session = session;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return error == null;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientValidationContext invalid(String error) {
|
||||
this.error = error;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -78,3 +78,4 @@ org.keycloak.crypto.HashSpi
|
|||
org.keycloak.vault.VaultSpi
|
||||
org.keycloak.crypto.CekManagementSpi
|
||||
org.keycloak.crypto.ContentEncryptionSpi
|
||||
org.keycloak.validation.ClientValidationSPI
|
|
@ -30,6 +30,7 @@ import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
|||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.validation.ValidationMessages;
|
||||
import org.keycloak.validation.ClientValidationUtil;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
|
@ -81,6 +82,11 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
|
||||
client.setSecret(clientModel.getSecret());
|
||||
|
||||
ClientValidationUtil.validate(session, clientModel, true, c -> {
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, c.getError(), Response.Status.BAD_REQUEST);
|
||||
});
|
||||
|
||||
String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel, registrationAuth);
|
||||
client.setRegistrationAccessToken(registrationAccessToken);
|
||||
|
||||
|
@ -140,6 +146,11 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
RepresentationToModel.updateClient(rep, client);
|
||||
RepresentationToModel.updateClientProtocolMappers(rep, client);
|
||||
|
||||
ClientValidationUtil.validate(session, client, false, c -> {
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, c.getError(), Response.Status.BAD_REQUEST);
|
||||
});
|
||||
|
||||
rep = ModelToRepresentation.toRepresentation(client, session);
|
||||
|
||||
if (auth.isRegistrationAccessToken()) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.authorization.admin.AuthorizationService;
|
|||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
|
@ -63,6 +64,7 @@ import org.keycloak.services.validation.ClientValidator;
|
|||
import org.keycloak.services.validation.PairwiseClientValidator;
|
||||
import org.keycloak.services.validation.ValidationMessages;
|
||||
import org.keycloak.utils.ProfileHelper;
|
||||
import org.keycloak.validation.ClientValidationUtil;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
|
@ -151,10 +153,16 @@ public class ClientResource {
|
|||
|
||||
try {
|
||||
updateClientFromRep(rep, client, session);
|
||||
|
||||
ClientValidationUtil.validate(session, client, false, c -> {
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw new ErrorResponseException(Errors.INVALID_INPUT ,c.getError(), Response.Status.BAD_REQUEST);
|
||||
});
|
||||
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
|
||||
return Response.noContent().build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
|
||||
return ErrorResponse.exists("Client already exists");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.authorization.admin.AuthorizationService;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -39,6 +40,7 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluato
|
|||
import org.keycloak.services.validation.ClientValidator;
|
||||
import org.keycloak.services.validation.PairwiseClientValidator;
|
||||
import org.keycloak.services.validation.ValidationMessages;
|
||||
import org.keycloak.validation.ClientValidationUtil;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
|
@ -203,6 +205,11 @@ public class ClientsResource {
|
|||
}
|
||||
}
|
||||
|
||||
ClientValidationUtil.validate(session, clientModel, true, c -> {
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw new ErrorResponseException(Errors.INVALID_INPUT, c.getError(), Response.Status.BAD_REQUEST);
|
||||
});
|
||||
|
||||
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build()).build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.services.util.ResolveRelative;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
public class DefaultClientValidationProvider implements ClientValidationProvider {
|
||||
|
||||
private ClientValidationContext context;
|
||||
|
||||
// TODO Before adding more validation consider using a library for validation
|
||||
@Override
|
||||
public void validate(ClientValidationContext context) {
|
||||
this.context = context;
|
||||
|
||||
try {
|
||||
validate(context.getClient());
|
||||
} catch (ValidationException e) {
|
||||
context.invalid(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(ClientModel client) throws ValidationException {
|
||||
String resolvedRootUrl = ResolveRelative.resolveRootUrl(context.getSession(), client.getRootUrl());
|
||||
String resolvedBaseUrl = ResolveRelative.resolveRelativeUri(context.getSession(), resolvedRootUrl, client.getBaseUrl());
|
||||
|
||||
validateRootUrl(resolvedRootUrl);
|
||||
validateBaseUrl(resolvedBaseUrl);
|
||||
}
|
||||
|
||||
private void validateRootUrl(String rootUrl) throws ValidationException {
|
||||
if (rootUrl != null && !rootUrl.isEmpty()) {
|
||||
basicHttpUrlCheck("rootUrl", rootUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBaseUrl(String baseUrl) throws ValidationException {
|
||||
if (baseUrl != null && !baseUrl.isEmpty()) {
|
||||
basicHttpUrlCheck("baseUrl", baseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void basicHttpUrlCheck(String field, String url) throws ValidationException {
|
||||
boolean valid = true;
|
||||
try {
|
||||
URI uri = new URL(url).toURI();
|
||||
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
|
||||
valid = false;
|
||||
}
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw new ValidationException("Invalid URL in " + field);
|
||||
}
|
||||
}
|
||||
|
||||
class ValidationException extends Exception {
|
||||
|
||||
public ValidationException(String message) {
|
||||
super(message, null, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2019 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.validation;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class DefaultClientValidationProviderFactory implements ClientValidationProviderFactory {
|
||||
|
||||
private final DefaultClientValidationProvider provider = new DefaultClientValidationProvider();
|
||||
|
||||
@Override
|
||||
public ClientValidationProvider create(KeycloakSession session) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.validation.DefaultClientValidationProviderFactory
|
|
@ -103,6 +103,67 @@ public class ClientTest extends AbstractAdminTest {
|
|||
Assert.assertNames(realm.clients().findAll(), "account", "account-console", "realm-management", "security-admin-console", "broker", "my-app", Constants.ADMIN_CLI_CLIENT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createClientValidation() {
|
||||
ClientRepresentation rep = new ClientRepresentation();
|
||||
rep.setClientId("my-app");
|
||||
rep.setDescription("my-app description");
|
||||
rep.setEnabled(true);
|
||||
|
||||
rep.setRootUrl("invalid");
|
||||
createClientExpectingValidationError(rep, "Invalid URL in rootUrl");
|
||||
|
||||
rep.setRootUrl(null);
|
||||
rep.setBaseUrl("invalid");
|
||||
createClientExpectingValidationError(rep, "Invalid URL in baseUrl");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateClientValidation() {
|
||||
ClientRepresentation rep = createClient();
|
||||
|
||||
rep.setClientId("my-app");
|
||||
rep.setDescription("my-app description");
|
||||
rep.setEnabled(true);
|
||||
|
||||
rep.setRootUrl("invalid");
|
||||
updateClientExpectingValidationError(rep, "Invalid URL in rootUrl");
|
||||
|
||||
rep.setRootUrl(null);
|
||||
rep.setBaseUrl("invalid");
|
||||
updateClientExpectingValidationError(rep, "Invalid URL in baseUrl");
|
||||
|
||||
ClientRepresentation stored = realm.clients().get(rep.getId()).toRepresentation();
|
||||
assertNull(stored.getRootUrl());
|
||||
assertNull(stored.getBaseUrl());
|
||||
}
|
||||
|
||||
private void createClientExpectingValidationError(ClientRepresentation rep, String expectedError) {
|
||||
Response response = realm.clients().create(rep);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
OAuth2ErrorRepresentation error = response.readEntity(OAuth2ErrorRepresentation.class);
|
||||
assertEquals("invalid_input", error.getError());
|
||||
assertEquals(expectedError, error.getErrorDescription());
|
||||
|
||||
assertNull(response.getLocation());
|
||||
|
||||
response.close();
|
||||
}
|
||||
|
||||
private void updateClientExpectingValidationError(ClientRepresentation rep, String expectedError) {
|
||||
try {
|
||||
realm.clients().get(rep.getId()).update(rep);
|
||||
fail("Expected exception");
|
||||
} catch (BadRequestException e) {
|
||||
Response response = e.getResponse();
|
||||
assertEquals(400, response.getStatus());
|
||||
OAuth2ErrorRepresentation error = response.readEntity(OAuth2ErrorRepresentation.class);
|
||||
assertEquals("invalid_input", error.getError());
|
||||
assertEquals(expectedError, error.getErrorDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeClient() {
|
||||
String id = createClient().getId();
|
||||
|
|
|
@ -116,7 +116,6 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
|
|||
ClientRepresentation clientRep = new ClientRepresentation();
|
||||
clientRep.setClientId(name);
|
||||
clientRep.setName(name);
|
||||
clientRep.setRootUrl("foo");
|
||||
clientRep.setProtocol("openid-connect");
|
||||
return clientRep;
|
||||
}
|
||||
|
|
|
@ -345,7 +345,6 @@ public class ClientScopeTest extends AbstractClientTest {
|
|||
ClientRepresentation clientRep = new ClientRepresentation();
|
||||
clientRep.setClientId("bar-client");
|
||||
clientRep.setName("bar-client");
|
||||
clientRep.setRootUrl("foo");
|
||||
clientRep.setProtocol("openid-connect");
|
||||
clientRep.setDefaultClientScopes(Collections.singletonList("foo-scope"));
|
||||
String clientDbId = createClient(clientRep);
|
||||
|
|
|
@ -125,7 +125,6 @@ public class PartialImportTest extends AbstractAuthTest {
|
|||
ClientRepresentation client = new ClientRepresentation();
|
||||
client.setClientId(CLIENT_ROLES_CLIENT);
|
||||
client.setName(CLIENT_ROLES_CLIENT);
|
||||
client.setRootUrl("foo");
|
||||
client.setProtocol("openid-connect");
|
||||
try (Response resp = testRealmResource().clients().create(client)) {
|
||||
|
||||
|
@ -274,7 +273,6 @@ public class PartialImportTest extends AbstractAuthTest {
|
|||
ClientRepresentation client = new ClientRepresentation();
|
||||
client.setClientId(CLIENT_PREFIX + i);
|
||||
client.setName(CLIENT_PREFIX + i);
|
||||
client.setRootUrl("foo");
|
||||
clients.add(client);
|
||||
if (withServiceAccounts) {
|
||||
client.setServiceAccountsEnabled(true);
|
||||
|
|
|
@ -24,11 +24,15 @@ import org.keycloak.client.registration.ClientRegistrationException;
|
|||
import org.keycloak.client.registration.HttpErrorException;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.services.clientregistration.ErrorCodes;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -156,6 +160,48 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
assertEquals(name, createdClient.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerClientValidation() throws IOException {
|
||||
authCreateClients();
|
||||
ClientRepresentation client = buildClient();
|
||||
client.setRootUrl("invalid");
|
||||
|
||||
try {
|
||||
registerClient(client);
|
||||
} catch (ClientRegistrationException e) {
|
||||
HttpErrorException c = (HttpErrorException) e.getCause();
|
||||
assertEquals(400, c.getStatusLine().getStatusCode());
|
||||
|
||||
OAuth2ErrorRepresentation error = JsonSerialization.readValue(c.getErrorResponse(), OAuth2ErrorRepresentation.class);
|
||||
|
||||
assertEquals("invalid_client_metadata", error.getError());
|
||||
assertEquals("Invalid URL in rootUrl", error.getErrorDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateClientValidation() throws IOException, ClientRegistrationException {
|
||||
registerClientAsAdmin();
|
||||
|
||||
ClientRepresentation client = reg.get(CLIENT_ID);
|
||||
client.setRootUrl("invalid");
|
||||
|
||||
try {
|
||||
reg.update(client);
|
||||
} catch (ClientRegistrationException e) {
|
||||
HttpErrorException c = (HttpErrorException) e.getCause();
|
||||
assertEquals(400, c.getStatusLine().getStatusCode());
|
||||
|
||||
OAuth2ErrorRepresentation error = JsonSerialization.readValue(c.getErrorResponse(), OAuth2ErrorRepresentation.class);
|
||||
|
||||
assertEquals("invalid_client_metadata", error.getError());
|
||||
assertEquals("Invalid URL in rootUrl", error.getErrorDescription());
|
||||
}
|
||||
|
||||
ClientRepresentation updatedClient = reg.get(CLIENT_ID);
|
||||
assertNull(updatedClient.getRootUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getClientAsAdmin() throws ClientRegistrationException {
|
||||
registerClientAsAdmin();
|
||||
|
|
Loading…
Reference in a new issue