diff --git a/pom.xml b/pom.xml index c8c0e0dd0f9..4a478968c40 100644 --- a/pom.xml +++ b/pom.xml @@ -1145,11 +1145,6 @@ keycloak-admin-v2-api ${project.version} - - org.keycloak - keycloak-admin-v2-providers - ${project.version} - org.keycloak keycloak-admin-v2-rest diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index 0dbe29b6a04..9a6a7dbfb76 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -424,17 +424,6 @@ - - org.keycloak - keycloak-admin-v2-providers - - - * - * - - - - org.keycloak keycloak-admin-v2-rest diff --git a/rest/admin-v2/api/pom.xml b/rest/admin-v2/api/pom.xml index 11d876a215d..46b1ad13bbd 100644 --- a/rest/admin-v2/api/pom.xml +++ b/rest/admin-v2/api/pom.xml @@ -20,6 +20,11 @@ + + org.keycloak + keycloak-services + provided + org.keycloak keycloak-server-spi @@ -40,5 +45,34 @@ jakarta.ws.rs jakarta.ws.rs-api + + org.mapstruct + mapstruct + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -AgeneratedTranslationFilesPath=${project.build.directory}/generated-translation-files + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + \ No newline at end of file diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructClientModelMapper.java b/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/MapStructClientModelMapper.java similarity index 100% rename from rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructClientModelMapper.java rename to rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/MapStructClientModelMapper.java diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructModelMapper.java b/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/MapStructModelMapper.java similarity index 100% rename from rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructModelMapper.java rename to rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/MapStructModelMapper.java diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapper.java b/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapper.java index 1c4f5d1019e..510b70b7787 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapper.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapper.java @@ -1,11 +1,7 @@ package org.keycloak.models.mapper; -import org.keycloak.provider.Provider; - -public interface ModelMapper extends Provider { +public interface ModelMapper { ClientModelMapper clients(); - default void close() { - } } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapperFactory.java b/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapperFactory.java deleted file mode 100644 index 2393d4a9eee..00000000000 --- a/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapperFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.models.mapper; - -import org.keycloak.provider.ProviderFactory; - -public interface ModelMapperFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapperSpi.java b/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapperSpi.java deleted file mode 100644 index 41fedc931de..00000000000 --- a/rest/admin-v2/api/src/main/java/org/keycloak/models/mapper/ModelMapperSpi.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.keycloak.models.mapper; - -import org.keycloak.common.Profile; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class ModelMapperSpi implements Spi { - public static final String NAME = "model-mapper"; - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return ModelMapper.class; - } - - @Override - public Class> getProviderFactoryClass() { - return ModelMapperFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - // Currently used only by Client Admin API v2 - return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2); - } -} diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/ClientRepresentation.java b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/ClientRepresentation.java index f2ee3cd63ab..345dee6e5d9 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/ClientRepresentation.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/ClientRepresentation.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import org.hibernate.validator.constraints.URL; import org.keycloak.representations.admin.v2.validation.CreateClient; @@ -167,7 +166,6 @@ public class ClientRepresentation extends BaseRepresentation { @JsonInclude(JsonInclude.Include.NON_ABSENT) public static class Auth { - @NotNull @JsonPropertyDescription("Whether authentication is enabled for this client") private Boolean enabled; @@ -216,7 +214,6 @@ public class ClientRepresentation extends BaseRepresentation { @JsonInclude(JsonInclude.Include.NON_ABSENT) public static class ServiceAccount { - @NotNull @JsonPropertyDescription("Whether the service account is enabled") private Boolean enabled; diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/services/Service.java b/rest/admin-v2/api/src/main/java/org/keycloak/services/Service.java index 7e46c4a0c8c..d6bcb813a67 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/services/Service.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/services/Service.java @@ -1,9 +1,7 @@ package org.keycloak.services; -import org.keycloak.provider.Provider; - /** * Service handling business logic for various user interfaces (REST API, GraphQL, GitOps,...) */ -public interface Service extends Provider { +public interface Service { } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientService.java b/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientService.java index df90ee0301a..5405c249b02 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientService.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientService.java @@ -7,6 +7,8 @@ import org.keycloak.models.RealmModel; import org.keycloak.representations.admin.v2.ClientRepresentation; import org.keycloak.services.Service; import org.keycloak.services.ServiceException; +import org.keycloak.services.resources.admin.ClientResource; +import org.keycloak.services.resources.admin.ClientsResource; public interface ClientService extends Service { @@ -27,14 +29,14 @@ public interface ClientService extends Service { record CreateOrUpdateResult(ClientRepresentation representation, boolean created) {} - Optional getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions); + Optional getClient(ClientResource clientResource, RealmModel realm, String clientId, ClientProjectionOptions projectionOptions); - Stream getClients(RealmModel realm, ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions); + Stream getClients(ClientsResource clientsResource, RealmModel realm, ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions); ClientRepresentation deleteClient(RealmModel realm, String clientId); Stream deleteClients(RealmModel realm, ClientSearchOptions searchOptions); - CreateOrUpdateResult createOrUpdate(RealmModel realm, ClientRepresentation client, boolean allowUpdate) throws ServiceException; + CreateOrUpdateResult createOrUpdate(ClientsResource clientsResource, ClientResource clientResource, RealmModel realm, ClientRepresentation client, boolean allowUpdate) throws ServiceException; } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientServiceFactory.java b/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientServiceFactory.java deleted file mode 100644 index 7d247fc41a9..00000000000 --- a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientServiceFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.services.client; - -import org.keycloak.provider.ProviderFactory; - -public interface ClientServiceFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientServiceSpi.java b/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientServiceSpi.java deleted file mode 100644 index 4fcbaabfcd3..00000000000 --- a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/ClientServiceSpi.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.services.client; - -import org.keycloak.common.Profile; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class ClientServiceSpi implements Spi { - public static final String NAME = "client-service"; - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return ClientService.class; - } - - @Override - public Class> getProviderFactoryClass() { - return ClientServiceFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2); - } -} diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/services/client/DefaultClientService.java b/rest/admin-v2/api/src/main/java/org/keycloak/services/client/DefaultClientService.java new file mode 100644 index 00000000000..3a35f573045 --- /dev/null +++ b/rest/admin-v2/api/src/main/java/org/keycloak/services/client/DefaultClientService.java @@ -0,0 +1,91 @@ +package org.keycloak.services.client; + +import jakarta.ws.rs.core.Response; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.mapper.ClientModelMapper; +import org.keycloak.models.mapper.MapStructModelMapper; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.admin.v2.ClientRepresentation; +import org.keycloak.representations.admin.v2.validation.CreateClientDefault; +import org.keycloak.services.ServiceException; +import org.keycloak.services.resources.admin.ClientResource; +import org.keycloak.services.resources.admin.ClientsResource; +import org.keycloak.validation.jakarta.HibernateValidatorProvider; +import org.keycloak.validation.jakarta.JakartaValidatorProvider; + +import java.util.Optional; +import java.util.stream.Stream; + +// TODO +public class DefaultClientService implements ClientService { + private final KeycloakSession session; + private final ClientModelMapper mapper; + private final JakartaValidatorProvider validator; + + public DefaultClientService(KeycloakSession session) { + this.session = session; + this.mapper = new MapStructModelMapper().clients(); + this.validator = new HibernateValidatorProvider(); + } + + @Override + public Optional getClient(ClientResource clientResource, RealmModel realm, String clientId, + ClientProjectionOptions projectionOptions) { + // TODO: is the access map on the representation needed + return Optional.ofNullable(clientResource).map(ClientResource::viewClientModel).map(mapper::fromModel); + } + + @Override + public Stream getClients(ClientsResource clientsResource, RealmModel realm, + ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, + ClientSortAndSliceOptions sortAndSliceOptions) { + // TODO: is the access map on the representation needed + return clientsResource.getClientModels(null, true, false, null, null, null).map(mapper::fromModel); + } + + @Override + public CreateOrUpdateResult createOrUpdate(ClientsResource clientsResource, ClientResource clientResource, + RealmModel realm, ClientRepresentation client, boolean allowUpdate) throws ServiceException { + boolean created = false; + ClientModel model = null; + if (clientResource != null) { + if (!allowUpdate) { + throw new ServiceException("Client already exists", Response.Status.CONFLICT); + } + model = clientResource.viewClientModel(); + mapper.toModel(model, client, realm); + var rep = ModelToRepresentation.toRepresentation(model, session); + clientResource.update(rep); + } else { + created = true; + validator.validate(client, CreateClientDefault.class); // TODO improve it to avoid second validation when we know it is create and not update + + // dummy add/remove to obtain a detached model + model = realm.addClient(client.getClientId()); + realm.removeClient(model.getId()); + + mapper.toModel(model, client, realm); + var rep = ModelToRepresentation.toRepresentation(model, session); + model = clientsResource.createClientModel(rep); + } + + var updated = mapper.fromModel(model); + + return new CreateOrUpdateResult(updated, created); + } + + @Override + public ClientRepresentation deleteClient(RealmModel realm, String clientId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Stream deleteClients(RealmModel realm, ClientSearchOptions searchOptions) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/services/error/ValidationExceptionHandler.java b/rest/admin-v2/api/src/main/java/org/keycloak/services/error/ValidationExceptionHandler.java similarity index 100% rename from rest/admin-v2/providers/src/main/java/org/keycloak/services/error/ValidationExceptionHandler.java rename to rest/admin-v2/api/src/main/java/org/keycloak/services/error/ValidationExceptionHandler.java diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProvider.java b/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProvider.java similarity index 84% rename from rest/admin-v2/providers/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProvider.java rename to rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProvider.java index 027ab764a16..5897c20c10e 100644 --- a/rest/admin-v2/providers/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProvider.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProvider.java @@ -1,5 +1,6 @@ package org.keycloak.validation.jakarta; +import jakarta.enterprise.inject.spi.CDI; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; @@ -8,10 +9,10 @@ import java.util.Set; import java.util.function.Function; public class HibernateValidatorProvider implements JakartaValidatorProvider { - private final Validator validator; - public HibernateValidatorProvider(Validator validator) { - this.validator = validator; + private final Validator validator = CDI.current().select(Validator.class).get(); + + public HibernateValidatorProvider() { } @Override @@ -35,8 +36,4 @@ public class HibernateValidatorProvider implements JakartaValidatorProvider { return validator; } - @Override - public void close() { - - } } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProvider.java b/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProvider.java index 2a7a4b3f032..0d22fe0ebd0 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProvider.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProvider.java @@ -3,12 +3,11 @@ package org.keycloak.validation.jakarta; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; -import org.keycloak.provider.Provider; import java.util.Set; import java.util.function.Function; -public interface JakartaValidatorProvider extends Provider { +public interface JakartaValidatorProvider { void validate(T object, Class... groups) throws ConstraintViolationException; diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProviderFactory.java b/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProviderFactory.java deleted file mode 100644 index 3c2365ea9dd..00000000000 --- a/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorProviderFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.validation.jakarta; - -import org.keycloak.provider.ProviderFactory; - -public interface JakartaValidatorProviderFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorSpi.java b/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorSpi.java deleted file mode 100644 index ebf314b99dc..00000000000 --- a/rest/admin-v2/api/src/main/java/org/keycloak/validation/jakarta/JakartaValidatorSpi.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.validation.jakarta; - -import org.keycloak.common.Profile; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class JakartaValidatorSpi implements Spi { - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return "jakarta-validator"; - } - - @Override - public Class getProviderClass() { - return JakartaValidatorProvider.class; - } - - @Override - public Class> getProviderFactoryClass() { - return JakartaValidatorProviderFactory.class; - } - - @Override - public boolean isEnabled() { - // Currently used only by Client Admin API v2 - return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2); - } -} diff --git a/rest/admin-v2/providers/src/main/resources/META-INF/beans.xml b/rest/admin-v2/api/src/main/resources/META-INF/beans.xml similarity index 100% rename from rest/admin-v2/providers/src/main/resources/META-INF/beans.xml rename to rest/admin-v2/api/src/main/resources/META-INF/beans.xml diff --git a/rest/admin-v2/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/rest/admin-v2/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index ed56a9f2837..00000000000 --- a/rest/admin-v2/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1,3 +0,0 @@ -org.keycloak.services.client.ClientServiceSpi -org.keycloak.models.mapper.ModelMapperSpi -org.keycloak.validation.jakarta.JakartaValidatorSpi \ No newline at end of file diff --git a/rest/admin-v2/pom.xml b/rest/admin-v2/pom.xml index e29537fb897..89ac6da1413 100644 --- a/rest/admin-v2/pom.xml +++ b/rest/admin-v2/pom.xml @@ -16,7 +16,6 @@ rest api - providers tests \ No newline at end of file diff --git a/rest/admin-v2/providers/pom.xml b/rest/admin-v2/providers/pom.xml deleted file mode 100644 index 386af0ce4a6..00000000000 --- a/rest/admin-v2/providers/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - 4.0.0 - - org.keycloak - keycloak-admin-v2-parent - 999.0.0-SNAPSHOT - - - keycloak-admin-v2-providers - Keycloak Admin API v2 Providers - - - 17 - 17 - 17 - UTF-8 - - - - - org.keycloak - keycloak-admin-v2-api - - - org.mapstruct - mapstruct - - - org.hibernate.validator - hibernate-validator - - - jakarta.enterprise - jakarta.enterprise.cdi-api - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -AgeneratedTranslationFilesPath=${project.build.directory}/generated-translation-files - - - - org.mapstruct - mapstruct-processor - ${org.mapstruct.version} - - - - - - - \ No newline at end of file diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructModelMapperFactory.java b/rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructModelMapperFactory.java deleted file mode 100644 index 9c6468d21fa..00000000000 --- a/rest/admin-v2/providers/src/main/java/org/keycloak/models/mapper/MapStructModelMapperFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.keycloak.models.mapper; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class MapStructModelMapperFactory implements ModelMapperFactory { - public static final String PROVIDER_ID = "default"; - private static ModelMapper SINGLETON; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public ModelMapper create(KeycloakSession session) { - if (SINGLETON == null) { - SINGLETON = new MapStructModelMapper(); - } - return SINGLETON; - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/services/client/DefaultClientService.java b/rest/admin-v2/providers/src/main/java/org/keycloak/services/client/DefaultClientService.java deleted file mode 100644 index ac9d6001e21..00000000000 --- a/rest/admin-v2/providers/src/main/java/org/keycloak/services/client/DefaultClientService.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.keycloak.services.client; - -import jakarta.ws.rs.core.Response; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.mapper.ClientModelMapper; -import org.keycloak.models.mapper.ModelMapper; -import org.keycloak.representations.admin.v2.ClientRepresentation; -import org.keycloak.representations.admin.v2.validation.CreateClientDefault; -import org.keycloak.services.ServiceException; -import org.keycloak.validation.jakarta.JakartaValidatorProvider; - -import java.util.Optional; -import java.util.stream.Stream; - -// TODO -public class DefaultClientService implements ClientService { - private final KeycloakSession session; - private final ClientModelMapper mapper; - private final JakartaValidatorProvider validator; - - public DefaultClientService(KeycloakSession session) { - this.session = session; - this.mapper = session.getProvider(ModelMapper.class).clients(); - this.validator = session.getProvider(JakartaValidatorProvider.class); - } - - @Override - public Optional getClient(RealmModel realm, String clientId, - ClientProjectionOptions projectionOptions) { - return Optional.ofNullable(realm.getClientByClientId(clientId)).map(mapper::fromModel); - } - - @Override - public Stream getClients(RealmModel realm, ClientProjectionOptions projectionOptions, - ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions) { - return realm.getClientsStream().map(mapper::fromModel); - } - - @Override - public CreateOrUpdateResult createOrUpdate(RealmModel realm, ClientRepresentation client, boolean allowUpdate) - throws ServiceException { - boolean created = false; - ClientModel model = realm.getClientByClientId(client.getClientId()); - if (model != null) { - if (!allowUpdate) { - throw new ServiceException("Client already exists", Response.Status.CONFLICT); - } - } else { - validator.validate(client, CreateClientDefault.class); // TODO improve it to avoid second validation when we know it is create and not update - model = realm.addClient(client.getClientId()); - created = true; - } - - // TODO: defaulting, validation, canonicalization - - mapper.toModel(model, client, realm); - - var updated = mapper.fromModel(model); - - return new CreateOrUpdateResult(updated, created); - } - - @Override - public ClientRepresentation deleteClient(RealmModel realm, String clientId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Stream deleteClients(RealmModel realm, ClientSearchOptions searchOptions) { - // TODO Auto-generated method stub - return null; - } - - - @Override - public void close() { - - } - -} diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/services/client/DefaultClientServiceFactory.java b/rest/admin-v2/providers/src/main/java/org/keycloak/services/client/DefaultClientServiceFactory.java deleted file mode 100644 index 7dec08bf0c2..00000000000 --- a/rest/admin-v2/providers/src/main/java/org/keycloak/services/client/DefaultClientServiceFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.services.client; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class DefaultClientServiceFactory implements ClientServiceFactory { - public static final String PROVIDER_ID = "default"; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public ClientService create(KeycloakSession session) { - return new DefaultClientService(session); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/providers/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProviderFactory.java b/rest/admin-v2/providers/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProviderFactory.java deleted file mode 100644 index e50f9c5dbba..00000000000 --- a/rest/admin-v2/providers/src/main/java/org/keycloak/validation/jakarta/HibernateValidatorProviderFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.keycloak.validation.jakarta; - -import jakarta.enterprise.inject.spi.CDI; -import jakarta.validation.Validator; -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class HibernateValidatorProviderFactory implements JakartaValidatorProviderFactory { - public static final String PROVIDER_ID = "default"; - private static HibernateValidatorProvider SINGLETON; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public JakartaValidatorProvider create(KeycloakSession session) { - if (SINGLETON == null) { - SINGLETON = new HibernateValidatorProvider(CDI.current().select(Validator.class).get()); - } - return SINGLETON; - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.models.mapper.ModelMapperFactory b/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.models.mapper.ModelMapperFactory deleted file mode 100644 index 1746852e27c..00000000000 --- a/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.models.mapper.ModelMapperFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.models.mapper.MapStructModelMapperFactory \ No newline at end of file diff --git a/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.services.client.ClientServiceFactory b/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.services.client.ClientServiceFactory deleted file mode 100644 index 9ef09d55c3f..00000000000 --- a/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.services.client.ClientServiceFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.services.client.DefaultClientServiceFactory \ No newline at end of file diff --git a/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.validation.jakarta.JakartaValidatorProviderFactory b/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.validation.jakarta.JakartaValidatorProviderFactory deleted file mode 100644 index c40b9ff7d96..00000000000 --- a/rest/admin-v2/providers/src/main/resources/META-INF/services/org.keycloak.validation.jakarta.JakartaValidatorProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.validation.jakarta.HibernateValidatorProviderFactory \ No newline at end of file diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApi.java index b0b55f2cdd8..fc39ec26aa5 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApi.java @@ -2,9 +2,8 @@ package org.keycloak.admin.api; import jakarta.ws.rs.Path; import org.keycloak.admin.api.realm.RealmsApi; -import org.keycloak.provider.Provider; -public interface AdminApi extends Provider { +public interface AdminApi { @Path("realms") RealmsApi realms(); diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApiFactory.java deleted file mode 100644 index c0e44d01504..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApiFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.admin.api; - -import org.keycloak.provider.ProviderFactory; - -public interface AdminApiFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApiSpi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApiSpi.java deleted file mode 100644 index 154b5949803..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminApiSpi.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.keycloak.admin.api; - -import org.keycloak.common.Profile; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class AdminApiSpi implements Spi { - public static final String PROVIDER_ID = "admin-api-root"; - - @Override - public String getName() { - return PROVIDER_ID; - } - - @Override - public Class getProviderClass() { - return AdminApi.class; - } - - @Override - public Class> getProviderFactoryClass() { - return AdminApiFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2); // There's currently only Client API for the new Admin API v2 - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminRootV2.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminRootV2.java index cafbb09782e..bb5dc27ecf7 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminRootV2.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminRootV2.java @@ -17,17 +17,10 @@ public class AdminRootV2 { @Context protected KeycloakSession session; - @Path("") - public AdminApi latestAdminApi() { - checkApiEnabled(); - // we could return the latest Admin API if no version is specified - return session.getProvider(AdminApi.class); - } - @Path("v2") public AdminApi adminApi() { checkApiEnabled(); - return session.getProvider(AdminApi.class); + return new DefaultAdminApi(session); } @Path("{any:.*}") diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApi.java index e79450077c4..3085988c1af 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApi.java @@ -7,11 +7,14 @@ import org.keycloak.admin.api.realm.DefaultRealmsApi; import org.keycloak.admin.api.realm.RealmsApi; import org.keycloak.models.AdminRoles; import org.keycloak.models.KeycloakSession; +import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.services.resources.admin.AdminAuth; import org.keycloak.services.resources.admin.AdminRoot; +import org.keycloak.services.resources.admin.RealmsAdminResource; public class DefaultAdminApi implements AdminApi { private final KeycloakSession session; + private final RealmsAdminResource realmsAdminResource; private final AdminAuth auth; public DefaultAdminApi(KeycloakSession session) { @@ -22,16 +25,13 @@ public class DefaultAdminApi implements AdminApi { if (!auth.getRealm().getName().equals(Config.getAdminRealm()) || !auth.hasRealmRole(AdminRoles.ADMIN)) { throw new NotAuthorizedException("Wrong permissions"); } + this.realmsAdminResource = new RealmsAdminResource(session, auth, new TokenManager()); } @Path("realms") @Override public RealmsApi realms() { - return new DefaultRealmsApi(session); + return new DefaultRealmsApi(session, realmsAdminResource); } - @Override - public void close() { - - } } diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApiFactory.java deleted file mode 100644 index 137dffcfa58..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminApiFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.admin.api; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class DefaultAdminApiFactory implements AdminApiFactory { - public static final String PROVIDER_ID = "default"; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public AdminApi create(KeycloakSession session) { - return new DefaultAdminApi(session); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApi.java index ebfd825c590..116386802bf 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApi.java @@ -10,12 +10,11 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import org.keycloak.admin.api.FieldValidation; -import org.keycloak.provider.Provider; import org.keycloak.representations.admin.v2.ClientRepresentation; import com.fasterxml.jackson.databind.JsonNode; -public interface ClientApi extends Provider { +public interface ClientApi { // TODO move these String CONTENT_TYPE_MERGE_PATCH = "application/merge-patch+json"; diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApiFactory.java deleted file mode 100644 index eb95f913aae..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApiFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.admin.api.client; - -import org.keycloak.provider.ProviderFactory; - -public interface ClientApiFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApiSpi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApiSpi.java deleted file mode 100644 index b246e00dbef..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientApiSpi.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.keycloak.admin.api.client; - -import org.keycloak.common.Profile; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class ClientApiSpi implements Spi { - public static final String NAME = "admin-api-client"; - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return ClientApi.class; - } - - @Override - public Class> getProviderFactoryClass() { - return ClientApiFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2); - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApi.java index d56e7701895..290927912ad 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApi.java @@ -8,7 +8,6 @@ import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.extensions.Extension; import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.keycloak.admin.api.FieldValidation; -import org.keycloak.provider.Provider; import org.keycloak.representations.admin.v2.ClientRepresentation; import jakarta.ws.rs.Consumes; @@ -22,7 +21,7 @@ import org.keycloak.services.resources.KeycloakOpenAPI; @Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS_V2) @Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "") -public interface ClientsApi extends Provider { +public interface ClientsApi { @GET @Produces(MediaType.APPLICATION_JSON) diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApiFactory.java deleted file mode 100644 index da3a180474a..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApiFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.admin.api.client; - -import org.keycloak.provider.ProviderFactory; - -public interface ClientsApiFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApiSpi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApiSpi.java deleted file mode 100644 index 23cf437c135..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/ClientsApiSpi.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.keycloak.admin.api.client; - -import org.keycloak.common.Profile; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class ClientsApiSpi implements Spi { - public static final String NAME = "admin-api-clients"; - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return ClientsApi.class; - } - - @Override - public Class> getProviderFactoryClass() { - return ClientsApiFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2); - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApi.java index d3d8f1e2ab9..e7ca291674b 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApi.java @@ -12,6 +12,9 @@ import org.keycloak.representations.admin.v2.ClientRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.ServiceException; import org.keycloak.services.client.ClientService; +import org.keycloak.services.client.DefaultClientService; +import org.keycloak.services.resources.admin.ClientResource; +import org.keycloak.services.resources.admin.ClientsResource; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -33,24 +36,35 @@ public class DefaultClientApi implements ClientApi { private final ClientService clientService; private HttpResponse response; - public DefaultClientApi(KeycloakSession session) { + private final ClientResource clientResource; + private final ClientsResource clientsResource; + private final String clientId; + + public DefaultClientApi(KeycloakSession session, ClientsResource clientsResource, ClientResource clientResource, String clientId) { this.session = session; this.realm = Objects.requireNonNull(session.getContext().getRealm()); this.client = Objects.requireNonNull(session.getContext().getClient()); - this.clientService = session.getProvider(ClientService.class); + this.clientService = new DefaultClientService(session); this.response = session.getContext().getHttpResponse(); + this.clientsResource = clientsResource; + this.clientResource = clientResource; + this.clientId = clientId; } @Override public ClientRepresentation getClient() { - return clientService.getClient(realm, client.getClientId(), null) + return clientService.getClient(clientResource, realm, client.getClientId(), null) .orElseThrow(() -> new NotFoundException("Cannot find the specified client")); } @Override public ClientRepresentation createOrUpdateClient(ClientRepresentation client, FieldValidation fieldValidation) { try { - var result = clientService.createOrUpdate(realm, client, true); + if (!Objects.equals(clientId, client.getClientId())) { + throw new WebApplicationException("cliendId in payload does not match the clientId in the path", Response.Status.BAD_REQUEST); + } + validateUnknownFields(fieldValidation, client, response); + var result = clientService.createOrUpdate(clientsResource, clientResource, realm, client, true); if (result.created()) { response.setStatus(Response.Status.CREATED.getStatusCode()); } @@ -79,16 +93,8 @@ public class DefaultClientApi implements ClientApi { updated = objectReader.readValue(patch); } - // TODO: reuse in the other methods - if (!updated.getAdditionalFields().isEmpty()) { - if (fieldValidation == null || fieldValidation == FieldValidation.Strict) { - // validation failed - throw new WebApplicationException("Payload contains unknown fields: " + updated.getAdditionalFields().keySet(), Response.Status.BAD_REQUEST); - } else if (fieldValidation == FieldValidation.Warn) { - response.addHeader("WARNING", "Payload contains unknown fields: " + updated.getAdditionalFields().keySet()); - } - } - return clientService.createOrUpdate(realm, updated, true).representation(); + validateUnknownFields(fieldValidation, updated, response); + return clientService.createOrUpdate(clientsResource, clientResource, realm, updated, true).representation(); } catch (JsonPatchException e) { // TODO: kubernetes uses 422 instead throw new WebApplicationException(e.getMessage(), Response.Status.BAD_REQUEST); @@ -99,8 +105,15 @@ public class DefaultClientApi implements ClientApi { } } - @Override - public void close() { - + static void validateUnknownFields(FieldValidation fieldValidation, ClientRepresentation rep, HttpResponse response) { + if (!rep.getAdditionalFields().isEmpty()) { + if (fieldValidation == null || fieldValidation == FieldValidation.Strict) { + // validation failed + throw new WebApplicationException("Payload contains unknown fields: " + rep.getAdditionalFields().keySet(), Response.Status.BAD_REQUEST); + } else if (fieldValidation == FieldValidation.Warn) { + response.addHeader("WARNING", "Payload contains unknown fields: " + rep.getAdditionalFields().keySet()); + } + } } + } diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApiFactory.java deleted file mode 100644 index baf95861c28..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientApiFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.admin.api.client; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class DefaultClientApiFactory implements ClientApiFactory { - public static final String PROVIDER_ID = "default"; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public ClientApi create(KeycloakSession session) { - return new DefaultClientApi(session); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApi.java index e8e4b7cc3f5..39a76fc598d 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApi.java @@ -5,7 +5,6 @@ import java.util.Optional; import java.util.stream.Stream; import jakarta.validation.Valid; -import jakarta.ws.rs.NotFoundException; import org.keycloak.admin.api.FieldValidation; import org.keycloak.http.HttpResponse; import org.keycloak.models.KeycloakSession; @@ -14,10 +13,14 @@ import org.keycloak.representations.admin.v2.ClientRepresentation; import org.keycloak.representations.admin.v2.validation.CreateClientDefault; import org.keycloak.services.ServiceException; import org.keycloak.services.client.ClientService; +import org.keycloak.services.client.DefaultClientService; +import org.keycloak.services.resources.admin.ClientsResource; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; + +import org.keycloak.validation.jakarta.HibernateValidatorProvider; import org.keycloak.validation.jakarta.JakartaValidatorProvider; public class DefaultClientsApi implements ClientsApi { @@ -26,26 +29,29 @@ public class DefaultClientsApi implements ClientsApi { private final HttpResponse response; private final ClientService clientService; private final JakartaValidatorProvider validator; + private final ClientsResource clientsResource; - public DefaultClientsApi(KeycloakSession session) { + public DefaultClientsApi(KeycloakSession session, ClientsResource clientsResource) { this.session = session; this.realm = Objects.requireNonNull(session.getContext().getRealm()); - this.clientService = session.getProvider(ClientService.class); + this.clientService = new DefaultClientService(session); this.response = session.getContext().getHttpResponse(); - this.validator = session.getProvider(JakartaValidatorProvider.class); + this.validator = new HibernateValidatorProvider(); + this.clientsResource = clientsResource; } @Override public Stream getClients() { - return clientService.getClients(realm, null, null, null); + return clientService.getClients(clientsResource, realm, null, null, null); } @Override public ClientRepresentation createClient(@Valid ClientRepresentation client, FieldValidation fieldValidation) { try { + DefaultClientApi.validateUnknownFields(fieldValidation, client, response); validator.validate(client, CreateClientDefault.class); response.setStatus(Response.Status.CREATED.getStatusCode()); - return clientService.createOrUpdate(realm, client, false).representation(); + return clientService.createOrUpdate(clientsResource, null, realm, client, false).representation(); } catch (ServiceException e) { throw new WebApplicationException(e.getMessage(), e.getSuggestedResponseStatus().orElse(Response.Status.BAD_REQUEST)); } @@ -53,13 +59,8 @@ public class DefaultClientsApi implements ClientsApi { @Override public ClientApi client(@PathParam("id") String clientId) { - var client = Optional.ofNullable(session.clients().getClientByClientId(realm, clientId)).orElseThrow(() -> new NotFoundException("Client cannot be found")); - session.getContext().setClient(client); - return session.getProvider(ClientApi.class); + var client = Optional.ofNullable(session.clients().getClientByClientId(realm, clientId)); + return new DefaultClientApi(session, clientsResource, client.map(c -> clientsResource.getClient(c.getId())).orElse(null), clientId); } - @Override - public void close() { - - } } diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApiFactory.java deleted file mode 100644 index 931fe3af6ba..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/client/DefaultClientsApiFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.admin.api.client; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class DefaultClientsApiFactory implements ClientsApiFactory { - public static final String PROVIDER_ID = "default"; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public ClientsApi create(KeycloakSession session) { - return new DefaultClientsApi(session); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApi.java index 00b3181f7ab..edf6b5b8855 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApi.java @@ -2,26 +2,23 @@ package org.keycloak.admin.api.realm; import jakarta.ws.rs.Path; import org.keycloak.admin.api.client.ClientsApi; +import org.keycloak.admin.api.client.DefaultClientsApi; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; - -import java.util.Objects; +import org.keycloak.services.resources.admin.RealmAdminResource; public class DefaultRealmApi implements RealmApi { private final KeycloakSession session; - private final RealmModel realm; + private final RealmAdminResource realmAdminResource; - public DefaultRealmApi(KeycloakSession session) { + public DefaultRealmApi(KeycloakSession session, RealmAdminResource realmAdmin) { this.session = session; - this.realm = Objects.requireNonNull(session.getContext().getRealm()); + this.realmAdminResource = realmAdmin; } @Path("clients") @Override public ClientsApi clients() { - return session.getProvider(ClientsApi.class); + return new DefaultClientsApi(session, realmAdminResource.getClients()); } - @Override - public void close() {} } diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApiFactory.java deleted file mode 100644 index a231426c480..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmApiFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.admin.api.realm; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class DefaultRealmApiFactory implements RealmApiFactory { - public static final String PROVIDER_ID = "default"; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public RealmApi create(KeycloakSession session) { - return new DefaultRealmApi(session); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApi.java index d673e9b8fcd..32514308f99 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApi.java @@ -1,29 +1,24 @@ package org.keycloak.admin.api.realm; -import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import org.keycloak.models.KeycloakSession; - -import java.util.Optional; +import org.keycloak.services.resources.admin.RealmsAdminResource; public class DefaultRealmsApi implements RealmsApi { private final KeycloakSession session; + private final RealmsAdminResource realmsAdminResource; - public DefaultRealmsApi(KeycloakSession session) { + public DefaultRealmsApi(KeycloakSession session, RealmsAdminResource realmsAdminResource) { this.session = session; + this.realmsAdminResource = realmsAdminResource; } @Path("{name}") @Override public RealmApi realm(@PathParam("name") String name) { - var realm = Optional.ofNullable(session.realms().getRealmByName(name)).orElseThrow(() -> new NotFoundException("Realm cannot be found")); - session.getContext().setRealm(realm); - return session.getProvider(RealmApi.class); + var realmAdmin = realmsAdminResource.getRealmAdmin(name); + return new DefaultRealmApi(session, realmAdmin); } - @Override - public void close() { - - } } diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApiFactory.java deleted file mode 100644 index c8a1a7370c1..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/DefaultRealmsApiFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.admin.api.realm; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -public class DefaultRealmsApiFactory implements RealmsApiFactory { - public static final String PROVIDER_ID = "default"; - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public RealmsApi create(KeycloakSession session) { - return new DefaultRealmsApi(session); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApi.java index 0074acad205..3a69618f44d 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApi.java @@ -2,9 +2,8 @@ package org.keycloak.admin.api.realm; import jakarta.ws.rs.Path; import org.keycloak.admin.api.client.ClientsApi; -import org.keycloak.provider.Provider; -public interface RealmApi extends Provider { +public interface RealmApi { @Path("clients") ClientsApi clients(); diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApiFactory.java deleted file mode 100644 index 54a4835530d..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApiFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.admin.api.realm; - -import org.keycloak.provider.ProviderFactory; - -public interface RealmApiFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApiSpi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApiSpi.java deleted file mode 100644 index 6c692bc39fa..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmApiSpi.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.keycloak.admin.api.realm; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -import static org.keycloak.admin.api.AdminRootV2.isAdminApiV2Enabled; - -public class RealmApiSpi implements Spi { - public static final String NAME = "admin-api-realm"; - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return RealmApi.class; - } - - @Override - public Class> getProviderFactoryClass() { - return RealmApiFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - return isAdminApiV2Enabled(); - } -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApi.java index 92a26a5b2d0..f3df56c23c7 100644 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApi.java +++ b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApi.java @@ -1,11 +1,9 @@ package org.keycloak.admin.api.realm; - import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; -import org.keycloak.provider.Provider; -public interface RealmsApi extends Provider { +public interface RealmsApi { @Path("{name}") RealmApi realm(@PathParam("name") String name); diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApiFactory.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApiFactory.java deleted file mode 100644 index aff3c8a2fd2..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApiFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.keycloak.admin.api.realm; - -import org.keycloak.provider.ProviderFactory; - -public interface RealmsApiFactory extends ProviderFactory { -} diff --git a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApiSpi.java b/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApiSpi.java deleted file mode 100644 index b26cce77cc3..00000000000 --- a/rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/realm/RealmsApiSpi.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.keycloak.admin.api.realm; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -import static org.keycloak.admin.api.AdminRootV2.isAdminApiV2Enabled; - -public class RealmsApiSpi implements Spi { - public static final String NAME = "admin-api-realms"; - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return RealmsApi.class; - } - - @Override - public Class> getProviderFactoryClass() { - return RealmsApiFactory.class; - } - - @Override - public boolean isInternal() { - return true; - } - - @Override - public boolean isEnabled() { - return isAdminApiV2Enabled(); - } -} diff --git a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.AdminApiFactory b/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.AdminApiFactory deleted file mode 100644 index 14f064e7d7e..00000000000 --- a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.AdminApiFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.api.DefaultAdminApiFactory \ No newline at end of file diff --git a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.client.ClientApiFactory b/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.client.ClientApiFactory deleted file mode 100644 index 310fd11a655..00000000000 --- a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.client.ClientApiFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.api.client.DefaultClientApiFactory \ No newline at end of file diff --git a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.client.ClientsApiFactory b/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.client.ClientsApiFactory deleted file mode 100644 index 9a5198083ae..00000000000 --- a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.client.ClientsApiFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.api.client.DefaultClientsApiFactory \ No newline at end of file diff --git a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.realm.RealmApiFactory b/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.realm.RealmApiFactory deleted file mode 100644 index 31e0a0ea889..00000000000 --- a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.realm.RealmApiFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.api.realm.DefaultRealmApiFactory \ No newline at end of file diff --git a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.realm.RealmsApiFactory b/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.realm.RealmsApiFactory deleted file mode 100644 index d37d3d5a413..00000000000 --- a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.admin.api.realm.RealmsApiFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.api.realm.DefaultRealmsApiFactory \ No newline at end of file diff --git a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index d84d8e9be37..00000000000 --- a/rest/admin-v2/rest/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1,5 +0,0 @@ -org.keycloak.admin.api.AdminApiSpi -org.keycloak.admin.api.realm.RealmsApiSpi -org.keycloak.admin.api.realm.RealmApiSpi -org.keycloak.admin.api.client.ClientsApiSpi -org.keycloak.admin.api.client.ClientApiSpi \ No newline at end of file diff --git a/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java b/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java index 694097b9d68..28d75eb7798 100644 --- a/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java +++ b/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java @@ -24,6 +24,7 @@ import org.apache.http.HttpMessage; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; @@ -126,6 +127,51 @@ public class ClientApiV2Test { } } + @Test + public void putFailsWithDifferentClientId() throws Exception { + HttpPut request = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account"); + setAuthHeader(request); + request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + + ClientRepresentation rep = new ClientRepresentation(); + rep.setClientId("other"); + + request.setEntity(new StringEntity(mapper.writeValueAsString(rep))); + + try (var response = client.execute(request)) { + assertEquals(400, response.getStatusLine().getStatusCode()); + } + } + + @Test + public void putCreateOrUpdates() throws Exception { + HttpPut request = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/other"); + setAuthHeader(request); + request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + + ClientRepresentation rep = new ClientRepresentation(); + rep.setEnabled(true); + rep.setClientId("other"); + rep.setDescription("I'm new"); + + request.setEntity(new StringEntity(mapper.writeValueAsString(rep))); + + try (var response = client.execute(request)) { + assertEquals(200, response.getStatusLine().getStatusCode()); + ClientRepresentation client = mapper.createParser(response.getEntity().getContent()).readValueAs(ClientRepresentation.class); + assertEquals("I'm new", client.getDescription()); + } + + rep.setDescription("I'm updated"); + request.setEntity(new StringEntity(mapper.writeValueAsString(rep))); + + try (var response = client.execute(request)) { + assertEquals(200, response.getStatusLine().getStatusCode()); + ClientRepresentation client = mapper.createParser(response.getEntity().getContent()).readValueAs(ClientRepresentation.class); + assertEquals("I'm updated", client.getDescription()); + } + } + @Test public void clientRepresentationValidation() throws Exception { HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 58341c9b487..571af77b0c3 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -194,6 +194,16 @@ public class ClientResource { @Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS) @Operation( summary = "Get representation of the client") public ClientRepresentation getClient() { + viewClientModel(); + + ClientRepresentation representation = ModelToRepresentation.toRepresentation(client, session); + + representation.setAccess(auth.clients().getAccess(client)); + + return representation; + } + + public ClientModel viewClientModel() { try { session.clientPolicy().triggerOnEvent(new AdminClientViewContext(client, auth.adminAuth())); } catch (ClientPolicyException cpe) { @@ -201,12 +211,7 @@ public class ClientResource { } auth.clients().requireView(client); - - ClientRepresentation representation = ModelToRepresentation.toRepresentation(client, session); - - representation.setAccess(auth.clients().getAccess(client)); - - return representation; + return client; } /** diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 00042f1dfe3..9108ab20770 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -119,6 +119,20 @@ public class ClientsResource { @QueryParam("q") String searchQuery, @Parameter(description = "the first result") @QueryParam("first") Integer firstResult, @Parameter(description = "the max results to return") @QueryParam("max") Integer maxResults) { + return ModelToRepresentation.filterValidRepresentations( + getClientModels(clientId, viewableOnly, search, searchQuery, firstResult, maxResults), c -> { + ClientRepresentation representation = ModelToRepresentation.toRepresentation(c, session); + representation.setAccess(auth.clients().getAccess(c)); + return representation; + }); + } + + public Stream getClientModels(String clientId, + boolean viewableOnly, + boolean search, + String searchQuery, + Integer firstResult, + Integer maxResults) { auth.clients().requireList(); boolean canView = AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm) || auth.clients().canView(); @@ -152,21 +166,7 @@ public class ClientsResource { throw new ErrorResponseException(Errors.INVALID_REQUEST, e.getMessage(), Response.Status.BAD_REQUEST); } - Stream s = ModelToRepresentation.filterValidRepresentations(clientModels, - c -> { - ClientRepresentation representation = null; - if (canView || auth.clients().canView(c)) { - representation = ModelToRepresentation.toRepresentation(c, session); - representation.setAccess(auth.clients().getAccess(c)); - } else if (!viewableOnly && auth.clients().canView(c)) { - representation = new ClientRepresentation(); - representation.setId(c.getId()); - representation.setClientId(c.getClientId()); - representation.setDescription(c.getDescription()); - } - - return representation; - }); + Stream s = clientModels.filter(m -> canView || auth.clients().canView(m)); if (!canView) { s = paginatedStream(s, firstResult, maxResults); @@ -196,6 +196,11 @@ public class ClientsResource { @APIResponse(responseCode = "409", description = "Conflict") }) public Response createClient(final ClientRepresentation rep) { + var created = createClientModel(rep); + return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(created.getId()).build()).build(); + } + + public ClientModel createClientModel(final ClientRepresentation rep) { auth.clients().requireManage(); try { @@ -232,7 +237,7 @@ public class ClientsResource { session.getContext().setClient(clientModel); session.clientPolicy().triggerOnEvent(new AdminClientRegisteredContext(clientModel, auth.adminAuth())); - return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build()).build(); + return clientModel; } catch (ModelDuplicateException e) { throw ErrorResponse.exists("Client " + rep.getClientId() + " already exists"); } catch (ClientPolicyException cpe) {