mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
task: use client v1 logic for v2 impl (#43982)
* task: use client v1 logic for v2 impl closes: #43733 Signed-off-by: Steve Hawkins <shawkins@redhat.com> * removing the provider module Signed-off-by: Steve Hawkins <shawkins@redhat.com> --------- Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
parent
c0be5c42b9
commit
63fc0eec28
5
pom.xml
5
pom.xml
@ -1145,11 +1145,6 @@
|
||||
<artifactId>keycloak-admin-v2-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-v2-providers</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-v2-rest</artifactId>
|
||||
|
||||
@ -424,17 +424,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-v2-providers</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-v2-rest</artifactId>
|
||||
|
||||
@ -20,6 +20,11 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
@ -40,5 +45,34 @@
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.enterprise</groupId>
|
||||
<artifactId>jakarta.enterprise.cdi-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<compilerArgument>
|
||||
-AgeneratedTranslationFilesPath=${project.build.directory}/generated-translation-files
|
||||
</compilerArgument>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ModelMapperFactory extends ProviderFactory<ModelMapper> {
|
||||
}
|
||||
@ -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<? extends ModelMapper> getProviderClass() {
|
||||
return ModelMapper.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<ModelMapper>> 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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
@ -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<ClientRepresentation> getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions);
|
||||
Optional<ClientRepresentation> getClient(ClientResource clientResource, RealmModel realm, String clientId, ClientProjectionOptions projectionOptions);
|
||||
|
||||
Stream<ClientRepresentation> getClients(RealmModel realm, ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions);
|
||||
Stream<ClientRepresentation> getClients(ClientsResource clientsResource, RealmModel realm, ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions);
|
||||
|
||||
ClientRepresentation deleteClient(RealmModel realm, String clientId);
|
||||
|
||||
Stream<ClientRepresentation> 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;
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.services.client;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ClientServiceFactory extends ProviderFactory<ClientService> {
|
||||
}
|
||||
@ -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<? extends ClientService> getProviderClass() {
|
||||
return ClientService.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<ClientService>> getProviderFactoryClass() {
|
||||
return ClientServiceFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2);
|
||||
}
|
||||
}
|
||||
@ -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<ClientRepresentation> 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<ClientRepresentation> 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<ClientRepresentation> deleteClients(RealmModel realm, ClientSearchOptions searchOptions) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
<T> void validate(T object, Class<?>... groups) throws ConstraintViolationException;
|
||||
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.validation.jakarta;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface JakartaValidatorProviderFactory extends ProviderFactory<JakartaValidatorProvider> {
|
||||
}
|
||||
@ -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<? extends Provider> getProviderClass() {
|
||||
return JakartaValidatorProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<?>> 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);
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
org.keycloak.services.client.ClientServiceSpi
|
||||
org.keycloak.models.mapper.ModelMapperSpi
|
||||
org.keycloak.validation.jakarta.JakartaValidatorSpi
|
||||
@ -16,7 +16,6 @@
|
||||
<modules>
|
||||
<module>rest</module>
|
||||
<module>api</module>
|
||||
<module>providers</module>
|
||||
<module>tests</module>
|
||||
</modules>
|
||||
</project>
|
||||
@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-v2-parent</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-admin-v2-providers</artifactId>
|
||||
<name>Keycloak Admin API v2 Providers</name>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-v2-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.enterprise</groupId>
|
||||
<artifactId>jakarta.enterprise.cdi-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<compilerArgument>
|
||||
-AgeneratedTranslationFilesPath=${project.build.directory}/generated-translation-files
|
||||
</compilerArgument>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<ClientRepresentation> getClient(RealmModel realm, String clientId,
|
||||
ClientProjectionOptions projectionOptions) {
|
||||
return Optional.ofNullable(realm.getClientByClientId(clientId)).map(mapper::fromModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ClientRepresentation> 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<ClientRepresentation> deleteClients(RealmModel realm, ClientSearchOptions searchOptions) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.models.mapper.MapStructModelMapperFactory
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.services.client.DefaultClientServiceFactory
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.validation.jakarta.HibernateValidatorProviderFactory
|
||||
@ -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();
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.admin.api;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface AdminApiFactory extends ProviderFactory<AdminApi> {
|
||||
}
|
||||
@ -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<? extends Provider> getProviderClass() {
|
||||
return AdminApi.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<AdminApi>> 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
|
||||
}
|
||||
}
|
||||
@ -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:.*}")
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.admin.api.client;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ClientApiFactory extends ProviderFactory<ClientApi> {
|
||||
}
|
||||
@ -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<? extends Provider> getProviderClass() {
|
||||
return ClientApi.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<ClientApi>> getProviderFactoryClass() {
|
||||
return ClientApiFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.admin.api.client;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ClientsApiFactory extends ProviderFactory<ClientsApi> {
|
||||
}
|
||||
@ -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<? extends Provider> getProviderClass() {
|
||||
return ClientsApi.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<ClientsApi>> getProviderFactoryClass() {
|
||||
return ClientsApiFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_ADMIN_API_V2);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<ClientRepresentation> 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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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() {}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.admin.api.realm;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface RealmApiFactory extends ProviderFactory<RealmApi> {
|
||||
}
|
||||
@ -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<? extends Provider> getProviderClass() {
|
||||
return RealmApi.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<RealmApi>> getProviderFactoryClass() {
|
||||
return RealmApiFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return isAdminApiV2Enabled();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package org.keycloak.admin.api.realm;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface RealmsApiFactory extends ProviderFactory<RealmsApi> {
|
||||
}
|
||||
@ -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<? extends Provider> getProviderClass() {
|
||||
return RealmsApi.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<RealmsApi>> getProviderFactoryClass() {
|
||||
return RealmsApiFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return isAdminApiV2Enabled();
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.admin.api.DefaultAdminApiFactory
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.admin.api.client.DefaultClientApiFactory
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.admin.api.client.DefaultClientsApiFactory
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.admin.api.realm.DefaultRealmApiFactory
|
||||
@ -1 +0,0 @@
|
||||
org.keycloak.admin.api.realm.DefaultRealmsApiFactory
|
||||
@ -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
|
||||
@ -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");
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<ClientModel> 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<ClientRepresentation> 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<ClientModel> 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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user