mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-05 22:39:52 -06:00
[Admin API v2] Skeleton prototype (#39322)
* Add new ClientRepresentation Co-authored-by: Peter Zaoral <pzaoral@redhat.com> Co-authored-by: Václav Muzikář <vmuzikar@redhat.com> Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add APIs Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add ApiModelMapper SPI Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add MapStruct as default ApiModelMapper Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add default APIs implementations Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Provide Service SPI and ClientService Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add default Keycloak services and Client service Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add ModelMapper to shared modules Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Implement Client service, add ServiceException class Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Use ClientService in Client REST API Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Update rest/admin-api/src/main/java/org/keycloak/admin/api/client/ClientsApi.java Co-authored-by: Václav Muzikář <vaclav@muzikari.cz> Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Fix ModelMapperSpi Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Use /admin/api/v2 as a root path Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Support latest API version by default Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Rename path param to comply with API spec Signed-off-by: Martin Bartoš <mabartos@redhat.com> --------- Signed-off-by: Martin Bartoš <mabartos@redhat.com> Co-authored-by: Peter Zaoral <pzaoral@redhat.com> Co-authored-by: Václav Muzikář <vmuzikar@redhat.com> Co-authored-by: Václav Muzikář <vaclav@muzikari.cz>
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
package org.keycloak.representations.admin.v2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ClientRepresentation {
|
||||
|
||||
@JsonProperty(required = true)
|
||||
@JsonPropertyDescription("ID uniquely identifying this client")
|
||||
private String clientId;
|
||||
|
||||
@JsonPropertyDescription("Human readable name of the client")
|
||||
private String displayName;
|
||||
|
||||
@JsonPropertyDescription("Human readable description of the client")
|
||||
private String description;
|
||||
|
||||
@JsonPropertyDescription("The protocol used to communicate with the client")
|
||||
private String protocol;
|
||||
|
||||
@JsonPropertyDescription("Whether this client is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@JsonPropertyDescription("URL to the application's homepage that is represented by this client")
|
||||
private String appUrl;
|
||||
|
||||
@JsonPropertyDescription("URLs that the browser can redirect to after login")
|
||||
private Set<String> appRedirectUrls;
|
||||
|
||||
@JsonPropertyDescription("Login flows that are enabled for this client")
|
||||
private Set<String> loginFlows;
|
||||
|
||||
@JsonPropertyDescription("Authentication configuration for this client")
|
||||
private Auth auth;
|
||||
|
||||
@JsonPropertyDescription("Web origins that are allowed to make requests to this client")
|
||||
private Set<String> webOrigins;
|
||||
|
||||
@JsonPropertyDescription("Roles associated with this client")
|
||||
private Set<String> roles;
|
||||
|
||||
@JsonPropertyDescription("Service account configuration for this client")
|
||||
private ServiceAccount serviceAccount;
|
||||
|
||||
public ClientRepresentation() {}
|
||||
|
||||
public ClientRepresentation(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getAppUrl() {
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
public void setAppUrl(String appUrl) {
|
||||
this.appUrl = appUrl;
|
||||
}
|
||||
|
||||
public Set<String> getAppRedirectUrls() {
|
||||
return appRedirectUrls;
|
||||
}
|
||||
|
||||
public void setAppRedirectUrls(Set<String> appRedirectUrls) {
|
||||
this.appRedirectUrls = appRedirectUrls;
|
||||
}
|
||||
|
||||
public Set<String> getLoginFlows() {
|
||||
return loginFlows;
|
||||
}
|
||||
|
||||
public void setLoginFlows(Set<String> loginFlows) {
|
||||
this.loginFlows = loginFlows;
|
||||
}
|
||||
|
||||
public Auth getAuth() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
public void setAuth(Auth auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public Set<String> getWebOrigins() {
|
||||
return webOrigins;
|
||||
}
|
||||
|
||||
public void setWebOrigins(Set<String> webOrigins) {
|
||||
this.webOrigins = webOrigins;
|
||||
}
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public ServiceAccount getServiceAccount() {
|
||||
return serviceAccount;
|
||||
}
|
||||
|
||||
public void setServiceAccount(ServiceAccount serviceAccount) {
|
||||
this.serviceAccount = serviceAccount;
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public static class Auth {
|
||||
|
||||
@JsonPropertyDescription("Whether authentication is enabled for this client")
|
||||
private Boolean enabled;
|
||||
|
||||
@JsonPropertyDescription("Which authentication method is used for this client")
|
||||
private String method;
|
||||
|
||||
@JsonPropertyDescription("Secret used to authenticate this client with Secret authentication")
|
||||
private String secret;
|
||||
|
||||
@JsonPropertyDescription("Public key used to authenticate this client with Signed JWT authentication")
|
||||
private String certificate;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
public void setCertificate(String certificate) {
|
||||
this.certificate = certificate;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public static class ServiceAccount {
|
||||
|
||||
@JsonPropertyDescription("Whether the service account is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@JsonPropertyDescription("Roles assigned to the service account")
|
||||
private Set<String> roles;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
pom.xml
12
pom.xml
@@ -219,6 +219,8 @@
|
||||
<!-- Used to test SAML Galleon feature-pack layers discovery -->
|
||||
<version.org.wildfly.glow>1.0.0.Alpha8</version.org.wildfly.glow>
|
||||
|
||||
<org.mapstruct.version>1.6.3</org.mapstruct.version>
|
||||
|
||||
<!-- Galleon -->
|
||||
<galleon.fork.embedded>true</galleon.fork.embedded>
|
||||
<galleon.log.time>true</galleon.log.time>
|
||||
@@ -385,6 +387,11 @@
|
||||
<artifactId>xsom</artifactId>
|
||||
<version>${org.glassfish.jaxb.xsom.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
@@ -1108,6 +1115,11 @@
|
||||
<artifactId>keycloak-admin-ui</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-rest-admin-ui-ext</artifactId>
|
||||
|
||||
@@ -135,6 +135,11 @@
|
||||
<artifactId>rdf-urdna</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SmallRye -->
|
||||
<dependency>
|
||||
<groupId>io.smallrye.config</groupId>
|
||||
@@ -394,6 +399,17 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-api</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Keycloak Dependencies-->
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-server-testdriver-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
40
rest/admin-api/pom.xml
Normal file
40
rest/admin-api/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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-rest-parent</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-admin-api</artifactId>
|
||||
<name>Keycloak Admin REST API v2</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-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
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 {
|
||||
|
||||
@Path("realms")
|
||||
RealmsApi realms();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.keycloak.admin.api;
|
||||
|
||||
import jakarta.ws.rs.OPTIONS;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.services.resources.admin.AdminCorsPreflightService;
|
||||
|
||||
@jakarta.ws.rs.ext.Provider
|
||||
@Path("admin/api")
|
||||
public class AdminRootV2 {
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Path("")
|
||||
public AdminApi latestAdminApi() {
|
||||
// we could return the latest Admin API if no version is specified
|
||||
return new DefaultAdminApi(session);
|
||||
}
|
||||
|
||||
@Path("v2")
|
||||
public AdminApi adminApi() {
|
||||
return new DefaultAdminApi(session);
|
||||
}
|
||||
|
||||
@Path("{any:.*}")
|
||||
@OPTIONS
|
||||
@Operation(hidden = true)
|
||||
public Object preFlight() {
|
||||
return new AdminCorsPreflightService();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.keycloak.admin.api;
|
||||
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.keycloak.admin.api.realm.DefaultRealmsApi;
|
||||
import org.keycloak.admin.api.realm.RealmsApi;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class DefaultAdminApi implements AdminApi {
|
||||
private final KeycloakSession session;
|
||||
|
||||
public DefaultAdminApi(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Path("realms")
|
||||
@Override
|
||||
public RealmsApi realms() {
|
||||
return new DefaultRealmsApi(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.keycloak.admin.api.client;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
|
||||
public interface ClientApi {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ClientRepresentation getClient(@QueryParam("runtime") Boolean isRuntime);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.keycloak.admin.api.client;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface ClientsApi extends Provider {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Stream<ClientRepresentation> getClients();
|
||||
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ClientRepresentation createOrUpdateClient(ClientRepresentation client);
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ClientRepresentation createClient(ClientRepresentation client);
|
||||
|
||||
@Path("{id}")
|
||||
ClientApi client(@PathParam("id") String id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.keycloak.admin.api.client;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
import org.keycloak.services.client.ClientService;
|
||||
|
||||
public class DefaultClientApi implements ClientApi {
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
private final String clientId;
|
||||
private final ClientService clientService;
|
||||
|
||||
public DefaultClientApi(KeycloakSession session, String clientId) {
|
||||
this.session = session;
|
||||
this.clientId = clientId;
|
||||
this.realm = session.getContext().getRealm();
|
||||
this.clientService = session.services().clients();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Override
|
||||
public ClientRepresentation getClient(@QueryParam("runtime") Boolean isRuntime) {
|
||||
return clientService.getClient(realm, clientId, isRuntime)
|
||||
.orElseThrow(() -> new NotFoundException("Cannot find the specified client"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.keycloak.admin.api.client;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.keycloak.http.HttpResponse;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
import org.keycloak.services.ServiceException;
|
||||
import org.keycloak.services.client.ClientService;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DefaultClientsApi implements ClientsApi {
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
private final HttpResponse response;
|
||||
private final ClientService clientService;
|
||||
|
||||
public DefaultClientsApi(KeycloakSession session, RealmModel realm) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.clientService = session.services().clients();
|
||||
this.response = session.getContext().getHttpResponse();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Override
|
||||
public Stream<ClientRepresentation> getClients() {
|
||||
return clientService.getClients(realm);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientRepresentation createOrUpdateClient(ClientRepresentation client) {
|
||||
try {
|
||||
// TODO return 200, or 201 if did not exist
|
||||
response.setStatus(Response.Status.OK.getStatusCode());
|
||||
return clientService.createOrUpdateClient(realm, client);
|
||||
} catch (ServiceException e) {
|
||||
throw new WebApplicationException(e.getMessage(), e.getSuggestedResponseStatus().orElse(Response.Status.BAD_REQUEST));
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientRepresentation createClient(ClientRepresentation client) {
|
||||
try {
|
||||
response.setStatus(Response.Status.CREATED.getStatusCode());
|
||||
return clientService.createClient(realm, client);
|
||||
} catch (ServiceException e) {
|
||||
throw new WebApplicationException(e.getMessage(), e.getSuggestedResponseStatus().orElse(Response.Status.BAD_REQUEST));
|
||||
}
|
||||
}
|
||||
|
||||
@Path("{id}")
|
||||
@Override
|
||||
public ClientApi client(@PathParam("id") String clientId) {
|
||||
return new DefaultClientApi(session, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.admin.api.realm;
|
||||
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
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.Optional;
|
||||
|
||||
public class DefaultRealmApi implements RealmApi {
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
|
||||
public DefaultRealmApi(KeycloakSession session, String name) {
|
||||
this.session = session;
|
||||
this.realm = Optional.ofNullable(session.realms().getRealmByName(name)).orElseThrow(() -> new NotFoundException("Realm cannot be found"));
|
||||
session.getContext().setRealm(realm);
|
||||
}
|
||||
|
||||
@Path("clients")
|
||||
@Override
|
||||
public ClientsApi clients() {
|
||||
return new DefaultClientsApi(session, realm);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.keycloak.admin.api.realm;
|
||||
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class DefaultRealmsApi implements RealmsApi {
|
||||
private final KeycloakSession session;
|
||||
|
||||
public DefaultRealmsApi(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Path("{name}")
|
||||
@Override
|
||||
public RealmApi realm(@PathParam("name") String name) {
|
||||
return new DefaultRealmApi(session, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.keycloak.admin.api.realm;
|
||||
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.keycloak.admin.api.client.ClientsApi;
|
||||
|
||||
public interface RealmApi {
|
||||
|
||||
@Path("clients")
|
||||
ClientsApi clients();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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 {
|
||||
|
||||
@Path("{name}")
|
||||
RealmApi realm(@PathParam("name") String name);
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>admin-api</module>
|
||||
<module>admin-ui-ext</module>
|
||||
</modules>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
|
||||
public interface ClientModelMapper {
|
||||
|
||||
ClientRepresentation fromModel(ClientModel model);
|
||||
|
||||
// ClientModel toModel(ClientModel baseModel, ClientRepresentation representation);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface ModelMapper extends Provider {
|
||||
|
||||
ClientModelMapper clients();
|
||||
|
||||
default void close() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ModelMapperFactory extends ProviderFactory<ModelMapper> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.keycloak.services;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface KeycloakServicesFactory extends ProviderFactory<KeycloakServices> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.keycloak.services;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class KeycloakServicesSpi implements Spi {
|
||||
public static final String NAME = "keycloak-services";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends KeycloakServices> getProviderClass() {
|
||||
return KeycloakServices.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory<KeycloakServices>> getProviderFactoryClass() {
|
||||
return KeycloakServicesFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.keycloak.services.client;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ClientServiceFactory extends ProviderFactory<ClientService> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.keycloak.services.client;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,9 @@ org.keycloak.cookie.CookieSpi
|
||||
org.keycloak.organization.OrganizationSpi
|
||||
org.keycloak.securityprofile.SecurityProfileSpi
|
||||
org.keycloak.logging.MappedDiagnosticContextSpi
|
||||
org.keycloak.services.KeycloakServicesSpi
|
||||
org.keycloak.services.client.ClientServiceSpi
|
||||
org.keycloak.models.mapper.ModelMapperSpi
|
||||
org.keycloak.models.policy.ResourceActionSpi
|
||||
org.keycloak.models.policy.ResourcePolicySpi
|
||||
org.keycloak.models.policy.ResourcePolicyConditionSpi
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.keycloak.models;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.services.KeycloakServices;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||
import org.keycloak.vault.VaultTranscriber;
|
||||
@@ -211,6 +212,8 @@ public interface KeycloakSession extends AutoCloseable {
|
||||
*/
|
||||
IdentityProviderStorageProvider identityProviders();
|
||||
|
||||
KeycloakServices services();
|
||||
|
||||
@Override
|
||||
void close();
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.keycloak.services;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.services.client.ClientService;
|
||||
|
||||
public interface KeycloakServices extends Provider {
|
||||
|
||||
ClientService clients();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
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 {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.services;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ServiceException extends RuntimeException {
|
||||
private Response.Status suggestedHttpResponseStatus;
|
||||
|
||||
public ServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public ServiceException(String message, Response.Status suggestedStatus) {
|
||||
this(message);
|
||||
this.suggestedHttpResponseStatus = suggestedStatus;
|
||||
}
|
||||
|
||||
public Optional<Response.Status> getSuggestedResponseStatus() {
|
||||
return Optional.ofNullable(suggestedHttpResponseStatus);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.keycloak.services.client;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
import org.keycloak.services.Service;
|
||||
import org.keycloak.services.ServiceException;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface ClientService extends Service {
|
||||
|
||||
Optional<ClientRepresentation> getClient(RealmModel realm, String clientId);
|
||||
|
||||
Optional<ClientRepresentation> getClient(RealmModel realm, String clientId, Boolean fullRepresentation);
|
||||
|
||||
Stream<ClientRepresentation> getClients(RealmModel realm);
|
||||
|
||||
ClientRepresentation createOrUpdateClient(RealmModel realm, ClientRepresentation client) throws ServiceException;
|
||||
|
||||
ClientRepresentation createClient(RealmModel realm, ClientRepresentation client) throws ServiceException;
|
||||
}
|
||||
@@ -81,7 +81,11 @@
|
||||
<groupId>org.twitter4j</groupId>
|
||||
<artifactId>twitter4j-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
@@ -290,6 +294,11 @@
|
||||
<artifactId>jboss-logging-processor</artifactId>
|
||||
<version>${jboss-logging-annotations.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.representations.admin.v2.ClientRepresentation;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mapper
|
||||
public interface MapStructClientModelMapper extends ClientModelMapper {
|
||||
@Mapping(target = "displayName", source = "name")
|
||||
@Mapping(target = "appUrl", source = "baseUrl")
|
||||
@Mapping(target = "appRedirectUrls", source = "redirectUris")
|
||||
@Mapping(target = "loginFlows", source = "authenticationFlowBindingOverrides", ignore = true)
|
||||
@Mapping(target = "auth", ignore = true) // TODO
|
||||
@Mapping(target = "roles", source = "rolesStream", qualifiedByName = "getRoleStrings")
|
||||
@Mapping(target = "serviceAccount.enabled", source = "serviceAccountsEnabled")
|
||||
@Mapping(target = "serviceAccount.roles", source = "rolesStream", qualifiedByName = "getServiceAccountRoles")
|
||||
@Override
|
||||
ClientRepresentation fromModel(ClientModel model);
|
||||
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void toModel(@MappingTarget ClientModel model, ClientRepresentation rep, @Context RealmModel realm);
|
||||
|
||||
@Named("getRoleStrings")
|
||||
default Set<String> getRoleStrings(Stream<RoleModel> stream) {
|
||||
return stream.map(RoleModel::getName).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Named("getServiceAccountRoles")
|
||||
default Set<String> getServiceAccountRoles(Stream<RoleModel> stream) {
|
||||
return stream.filter(f -> true) //TODO check roles for SA
|
||||
.map(RoleModel::getName)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.keycloak.models.mapper;
|
||||
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
public class MapStructModelMapper implements ModelMapper {
|
||||
private final MapStructClientModelMapper clientMapper;
|
||||
|
||||
public MapStructModelMapper() {
|
||||
this.clientMapper = Mappers.getMapper(MapStructClientModelMapper.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModelMapper clients() {
|
||||
return clientMapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.keycloak.services;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.services.client.ClientService;
|
||||
|
||||
public class DefaultKeycloakServices implements KeycloakServices {
|
||||
private final KeycloakSession session;
|
||||
private ClientService clients;
|
||||
|
||||
public DefaultKeycloakServices(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientService clients() {
|
||||
if (clients == null) {
|
||||
clients = session.getProvider(ClientService.class);
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.keycloak.services;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
public class DefaultKeycloakServicesFactory implements KeycloakServicesFactory {
|
||||
public static final String PROVIDER_ID = "default";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakServices create(KeycloakSession session) {
|
||||
return new DefaultKeycloakServices(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,10 @@ import org.keycloak.models.UserLoginFailureProvider;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
|
||||
import org.keycloak.provider.InvalidationHandler.ObjectType;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||
import org.keycloak.storage.DatastoreProvider;
|
||||
@@ -84,6 +84,7 @@ public abstract class DefaultKeycloakSession implements KeycloakSession {
|
||||
private TokenManager tokenManager;
|
||||
private VaultTranscriber vaultTranscriber;
|
||||
private ClientPolicyManager clientPolicyManager;
|
||||
private KeycloakServices services;
|
||||
private boolean closed = false;
|
||||
|
||||
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
||||
@@ -340,6 +341,14 @@ public abstract class DefaultKeycloakSession implements KeycloakSession {
|
||||
return clientPolicyManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakServices services() {
|
||||
if (services == null) {
|
||||
services = getProvider(KeycloakServices.class);
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DefaultKeycloakSession.class);
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.keycloak.services.client;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
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.services.ServiceException;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
// TODO
|
||||
public class DefaultClientService implements ClientService {
|
||||
private final KeycloakSession session;
|
||||
private final ClientModelMapper mapper;
|
||||
|
||||
public DefaultClientService(KeycloakSession session) {
|
||||
this.session = session;
|
||||
this.mapper = session.getProvider(ModelMapper.class).clients();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientRepresentation> getClient(RealmModel realm, String clientId) {
|
||||
return Optional.ofNullable(realm.getClientByClientId(clientId)).map(mapper::fromModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientRepresentation> getClient(RealmModel realm, String clientId, Boolean fullRepresentation) {
|
||||
// TODO reduced client rep
|
||||
return fullRepresentation != null && fullRepresentation ? getClient(realm, clientId) : Optional.of(getTestReducedClientRep(clientId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ClientRepresentation> getClients(RealmModel realm) {
|
||||
return realm.getClientsStream().map(mapper::fromModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRepresentation createOrUpdateClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
|
||||
return null; // TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRepresentation createClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
|
||||
if (realm.getClientByClientId(client.getClientId()) != null) {
|
||||
throw new ServiceException("Client already exists", Response.Status.CONFLICT);
|
||||
}
|
||||
|
||||
var model = realm.addClient(client.getClientId());
|
||||
return mapper.fromModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
// TODO tested reduced client representation
|
||||
private static ClientRepresentation getTestReducedClientRep(String clientId) {
|
||||
return new ClientRepresentation(clientId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.models.mapper.MapStructModelMapperFactory
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.services.DefaultKeycloakServicesFactory
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.services.client.DefaultClientServiceFactory
|
||||
Reference in New Issue
Block a user