fix: creating a cleaner module for use by java clients (#47874)

* fix: minimizing the dependencies for the rest module

closes: #48114

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* renaming the modules

also remove jsonnode logic from the oas filter and the databind
dependency

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* addressing review comments

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins
2026-04-16 09:18:41 -04:00
committed by GitHub
parent 1e60dfb551
commit e9f593020a
69 changed files with 187 additions and 217 deletions
@@ -0,0 +1,65 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.common.constants;
/**
* Class of constants relating to the OpenAPI annotations in Keycloak and the Keycloak Admin REST API
*/
public class KeycloakOpenAPI {
protected KeycloakOpenAPI() { }
public static class Profiles {
public static final String ADMIN = "x-smallrye-profile-admin";
private Profiles() { }
}
public static class Admin {
private Admin() { }
public static class Tags {
public static final String ATTACK_DETECTION = "Attack Detection";
public static final String AUTHENTICATION_MANAGEMENT = "Authentication Management";
public static final String CLIENTS = "Clients";
public static final String CLIENTS_V2 = "Clients (v2)";
public static final String CLIENT_ATTRIBUTE_CERTIFICATE = "Client Attribute Certificate";
public static final String CLIENT_INITIAL_ACCESS = "Client Initial Access";
public static final String CLIENT_REGISTRATION_POLICY = "Client Registration Policy";
public static final String CLIENT_ROLE_MAPPINGS = "Client Role Mappings";
public static final String CLIENT_SCOPES = "Client Scopes";
public static final String COMPONENT = "Component";
public static final String GROUPS = "Groups";
public static final String IDENTITY_PROVIDERS = "Identity Providers";
public static final String KEY = "Key";
public static final String PROTOCOL_MAPPERS = "Protocol Mappers";
public static final String REALMS_ADMIN = "Realms Admin";
public static final String ROLES = "Roles";
public static final String ROLES_BY_ID = "Roles (by ID)";
public static final String ROLE_MAPPER = "Role Mapper";
public static final String ROOT = "Root";
public static final String SCOPE_MAPPINGS = "Scope Mappings";
public static final String USERS = "Users";
public static final String ORGANIZATIONS = "Organizations";
public static final String WORKFLOWS = "Workflows";
private Tags() { }
}
}
}
+1 -1
View File
@@ -104,7 +104,7 @@
</dependencySourceIncludes>
<dependencySourceExcludes>
<dependencySourceExclude>org.keycloak:keycloak-operator</dependencySourceExclude>
<dependencySourceExclude>org.keycloak:keycloak-admin-v2-rest</dependencySourceExclude>
<dependencySourceExclude>org.keycloak:keycloak-admin-v2-api</dependencySourceExclude>
</dependencySourceExcludes>
</configuration>
<executions>
+1 -1
View File
@@ -61,7 +61,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-rest</artifactId>
<artifactId>keycloak-admin-v2-api</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
+1 -2
View File
@@ -54,8 +54,7 @@
<!-- required during compilation by the exec-maven-plugin as we need the OpenAPI document stored in the resources -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-rest</artifactId>
<version>${project.version}</version>
<artifactId>keycloak-admin-v2-services</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
+1 -1
View File
@@ -27,7 +27,7 @@
<!-- Enforce build order: openapi.yaml must be generated before JS build -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-rest</artifactId>
<artifactId>keycloak-admin-v2-services</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
-4
View File
@@ -123,10 +123,6 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-api</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-rest</artifactId>
</dependency>
<!-- Test -->
<dependency>
+1 -1
View File
@@ -1121,7 +1121,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-rest</artifactId>
<artifactId>keycloak-admin-v2-services</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
+1 -1
View File
@@ -424,7 +424,7 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-rest</artifactId>
<artifactId>keycloak-admin-v2-services</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
@@ -19,8 +19,6 @@ package org.keycloak.quarkus.runtime.configuration;
import java.util.List;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import picocli.CommandLine;
/**
@@ -38,9 +36,6 @@ public class KcUnmatchedArgumentException extends CommandLine.UnmatchedArgumentE
@Override
public List<String> getSuggestions() {
// filter out disabled mappers
return super.getSuggestions().stream()
.filter(f -> PropertyMappers.getKcKeyFromCliKey(f).filter(PropertyMappers::isDisabledMapper).isEmpty())
.toList();
return super.getSuggestions();
}
}
+18 -41
View File
@@ -10,7 +10,7 @@
</parent>
<artifactId>keycloak-admin-v2-api</artifactId>
<name>Keycloak Admin API v2 Interfaces</name>
<name>Keycloak Admin API v2 APIs</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
@@ -22,52 +22,29 @@
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<artifactId>keycloak-common</artifactId>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.openapi</groupId>
<artifactId>microprofile-openapi-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>provided</scope>
</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>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,4 +1,4 @@
package org.keycloak.services;
package org.keycloak.admin.api;
public class PatchTypeNames {
public static final String JSON_MERGE = "application/merge-patch+json";
@@ -12,8 +12,8 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.services.PatchTypeNames;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
@@ -12,8 +12,8 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.common.constants.KeycloakOpenAPI;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.services.resources.KeycloakOpenAPI;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
@@ -6,7 +6,6 @@ import java.util.Set;
import jakarta.validation.constraints.NotBlank;
import org.keycloak.representations.admin.v2.validation.ClientUuidProvider;
import org.keycloak.representations.admin.v2.validation.PatchClient;
import org.keycloak.representations.admin.v2.validation.ProtocolUnmodified;
import org.keycloak.representations.admin.v2.validation.PutClient;
@@ -30,7 +29,7 @@ import org.hibernate.validator.constraints.URL;
@JsonSubTypes.Type(value = OIDCClientRepresentation.class, name = OIDCClientRepresentation.PROTOCOL),
@JsonSubTypes.Type(value = SAMLClientRepresentation.class, name = SAMLClientRepresentation.PROTOCOL)
})
@UuidUnmodified(uuidProvider = ClientUuidProvider.class, groups = {PutClient.class, PatchClient.class})
@UuidUnmodified(groups = {PutClient.class, PatchClient.class})
@ProtocolUnmodified(groups = {PutClient.class, PatchClient.class})
@ValidRedirectUris
public abstract class BaseClientRepresentation extends BaseRepresentation implements RepresentationWithUuid {
@@ -141,7 +140,9 @@ public abstract class BaseClientRepresentation extends BaseRepresentation implem
@Override
public boolean equals(Object o) {
if (!(o instanceof BaseClientRepresentation that)) return false;
if (!(o instanceof BaseClientRepresentation that)) {
return false;
}
return Objects.equals(uuid, that.uuid) && Objects.equals(clientId, that.clientId) && Objects.equals(displayName, that.displayName) && Objects.equals(description, that.description) && Objects.equals(enabled, that.enabled) && Objects.equals(appUrl, that.appUrl) && Objects.equals(redirectUris, that.redirectUris) && Objects.equals(roles, that.roles);
}
@@ -15,11 +15,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* Validation constraint that requires the {@link OIDCClientRepresentation.Auth#getSecret()} is not blank
* when {@link OIDCClientRepresentation.Auth#getMethod()} is the (JWT) client secret.
*
* @see ClientSecretNotBlankValidator#isClientSecret(String)
*/
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = ClientSecretNotBlankValidator.class)
@Constraint(validatedBy = {})
public @interface ClientSecretNotBlank {
String message() default "Client secret must not be blank";
@@ -11,8 +11,8 @@ import jakarta.validation.Payload;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ProtocolUnmodifiedValidator.class})
@Documented
@Constraint(validatedBy = {})
public @interface ProtocolUnmodified {
String message() default "protocol cannot be changed for an existing client";
Class<?>[] groups() default {};
@@ -16,11 +16,10 @@ import jakarta.validation.Payload;
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {UuidUnmodifiedValidator.class})
@Documented
@Constraint(validatedBy = {})
public @interface UuidUnmodified {
String message() default "UUID is server-managed and must not be user-specified";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends UuidProvider> uuidProvider();
}
@@ -25,7 +25,7 @@ import jakarta.validation.Payload;
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ValidRedirectUrisValidator.class})
@Constraint(validatedBy = {})
@Documented
public @interface ValidRedirectUris {
String message() default "Invalid redirect URI";
+1 -1
View File
@@ -14,8 +14,8 @@
<packaging>pom</packaging>
<modules>
<module>rest</module>
<module>api</module>
<module>services</module>
<module>tests</module>
</modules>
</project>
@@ -9,8 +9,8 @@
<version>999.0.0-SNAPSHOT</version>
</parent>
<artifactId>keycloak-admin-v2-rest</artifactId>
<name>Keycloak Admin API v2 REST Layer</name>
<artifactId>keycloak-admin-v2-services</artifactId>
<name>Keycloak Admin API v2 Services</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
@@ -24,25 +24,42 @@
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-v2-api</artifactId>
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
<artifactId>keycloak-admin-v2-api</artifactId>
</dependency>
<!-- used by OAS Filter during OpenAPI spec generation -->
<dependency>
@@ -55,6 +72,15 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>
-AgeneratedTranslationFilesPath=${project.build.directory}/generated-translation-files
</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-open-api-maven-plugin</artifactId>
@@ -16,7 +16,6 @@ import org.keycloak.OAuth2Constants;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JsonNode;
import org.eclipse.microprofile.openapi.OASFactory;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.OpenAPI;
@@ -28,14 +27,10 @@ import org.eclipse.microprofile.openapi.models.security.SecurityScheme;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import static org.keycloak.services.PatchTypeNames.JSON_MERGE;
import static org.keycloak.utils.StringUtil.isNullOrEmpty;
public class OASModelFilter implements OASFilter {
@@ -44,7 +39,6 @@ public class OASModelFilter implements OASFilter {
private final Map<String, ClassInfo> simpleNameToClassInfoMap = new HashMap<>();
public static final String REF_PREFIX = "#/components/schemas/";
private static final DotName JSON_NODE = DotName.createSimple(JsonNode.class);
public OASModelFilter(IndexView indexView) {
log.debug("Index size: " + indexView.getKnownClasses().size());
@@ -70,8 +64,6 @@ public class OASModelFilter implements OASFilter {
removeSchemaAndRefs(openAPI, "BaseRepresentation");
fixJsonMergePatchRequestObject(openAPI);
Map<String, Set<Schema>> discriminatorPropertiesToBeAdded = new HashMap<>();
// Follows https://swagger.io/docs/specification/v3_0/data-models/inheritance-and-polymorphism/
@@ -129,57 +121,6 @@ public class OASModelFilter implements OASFilter {
setter.accept(filtered.isEmpty() ? null : filtered);
}
/**
* Currently, if endpoint consumes 'application/merge-patch+json' and the request object is 'JsonNode',
* SmallRye OpenAPI generates array type schema for the endpoint.
* See <a href="https://github.com/smallrye/smallrye-open-api/issues/2494">issue 2494</a> for more context.
* What we need is either no schema, or an object with additional properties, so that the generated client
* doesn't have empty body. This method removes schema.
*/
private void fixJsonMergePatchRequestObject(OpenAPI openAPI) {
if (openAPI.getPaths() == null) {
return;
}
openAPI.getPaths().getPathItems().forEach((path, pathItem) -> {
if (pathItem.getPATCH() != null && pathItem.getPATCH().getRequestBody() != null) {
var patchOp = pathItem.getPATCH();
var requestBody = patchOp.getRequestBody();
if (requestBody.getContent() != null && requestBody.getContent().getMediaType(JSON_MERGE) != null
&& hasJsonNodeParameter(patchOp.getOperationId())) {
var mediaTypeObject = requestBody.getContent().getMediaType(JSON_MERGE);
mediaTypeObject.setSchema(null);
log.debugf("Removed request body schema from PATCH path '%s' operation '%s' using content type '%s'", path, patchOp.getOperationId(), JSON_MERGE);
}
}
});
}
/**
* Detects REST interface method which name matches the operation name.
*/
private boolean hasJsonNodeParameter(String operationId) {
if (operationId == null) {
return false;
}
for (ClassInfo classInfo : simpleNameToClassInfoMap.values()) {
for (MethodInfo method : classInfo.methods()) {
if (method.name().equals(operationId)) {
for (Type paramType : method.parameterTypes()) {
if (JSON_NODE.equals(paramType.name())) {
log.debugf("Method '%s#%s' has parameter with type '%s'", classInfo.name(), method.name());
return true;
}
}
}
}
}
return false;
}
/**
* Adds discriminator and oneOf references to parent schemas that have Jackson @JsonTypeInfo
* and @JsonSubTypes annotations. This enables OpenAPI generators to create proper class
@@ -1,4 +1,4 @@
package org.keycloak.representations.admin.v2.validation;
package org.keycloak.representations.admin.v2.validators;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
@@ -6,6 +6,7 @@ import jakarta.validation.ConstraintValidatorContext;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
import org.keycloak.representations.admin.v2.OIDCClientRepresentation;
import org.keycloak.representations.admin.v2.validation.ClientSecretNotBlank;
import org.keycloak.utils.StringUtil;
/**
@@ -1,4 +1,4 @@
package org.keycloak.representations.admin.v2.validation;
package org.keycloak.representations.admin.v2.validators;
import java.util.Optional;
@@ -1,4 +1,4 @@
package org.keycloak.representations.admin.v2.validation;
package org.keycloak.representations.admin.v2.validators;
import java.util.Optional;
@@ -7,6 +7,7 @@ import jakarta.validation.ConstraintValidatorContext;
import org.keycloak.models.ClientModel;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.representations.admin.v2.validation.ProtocolUnmodified;
import org.keycloak.validation.jakarta.ValidationContext;
public class ProtocolUnmodifiedValidator implements ConstraintValidator<ProtocolUnmodified, BaseClientRepresentation> {
@@ -1,4 +1,4 @@
package org.keycloak.representations.admin.v2.validation;
package org.keycloak.representations.admin.v2.validators;
import org.keycloak.representations.admin.v2.RepresentationWithUuid;
import org.keycloak.validation.jakarta.ValidationContext;
@@ -1,9 +1,11 @@
package org.keycloak.representations.admin.v2.validation;
package org.keycloak.representations.admin.v2.validators;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.representations.admin.v2.RepresentationWithUuid;
import org.keycloak.representations.admin.v2.validation.UuidUnmodified;
import org.keycloak.validation.jakarta.ValidationContext;
/**
@@ -19,19 +21,16 @@ import org.keycloak.validation.jakarta.ValidationContext;
*/
public class UuidUnmodifiedValidator implements ConstraintValidator<UuidUnmodified, RepresentationWithUuid> {
private UuidProvider uuidProvider;
@Override
public void initialize(UuidUnmodified constraintAnnotation) {
try {
uuidProvider = constraintAnnotation.uuidProvider().getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to instantiate UUID provider: " + constraintAnnotation.uuidProvider().getName(), e);
}
}
@Override
public boolean isValid(RepresentationWithUuid representation, ConstraintValidatorContext context) {
Class<?> type = representation.getClass();
UuidProvider uuidProvider = null;
if (BaseClientRepresentation.class.isAssignableFrom(type)) {
uuidProvider = new ClientUuidProvider();
} else {
throw new AssertionError("No UuidProvider defined for " + type);
}
String providedUuid = representation.getUuid();
if (providedUuid == null || providedUuid.isEmpty()) { // no UUID provided, so nothing to validate
return true;
@@ -41,7 +40,9 @@ public class UuidUnmodifiedValidator implements ConstraintValidator<UuidUnmodifi
String persistedUuid = uuidProvider.getPersistedUuid(validationContext, representation);
if (persistedUuid != null) { // resource exists
if (persistedUuid.equals(providedUuid)) return true;
if (persistedUuid.equals(providedUuid)) {
return true;
}
} else if (!uuidProvider.uuidExists(validationContext, providedUuid)) { // additional check for PUT create to check the resource was just not renamed
return true;
}
@@ -1,4 +1,4 @@
package org.keycloak.representations.admin.v2.validation;
package org.keycloak.representations.admin.v2.validators;
import java.util.Set;
import java.util.regex.Pattern;
@@ -7,6 +7,7 @@ import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.representations.admin.v2.validation.ValidRedirectUris;
/**
* Validates redirect URIs according to Keycloak's redirect URI rules.
@@ -1,14 +1,13 @@
package org.keycloak.admin.api;
package org.keycloak.rest.admin.api;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.keycloak.admin.api.AdminApi;
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 org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.rest.admin.api.client.DefaultClientsApi;
import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.resources.admin.RealmAdminResource;
import org.keycloak.services.resources.admin.RealmsAdminResource;
@@ -32,9 +31,8 @@ public class DefaultAdminApi implements AdminApi {
this.realmAdminResource = new RealmsAdminResource(session, authInfo, new TokenManager()).getRealmAdmin(realmName);
}
@Path("clients/{version:v\\d+}")
@Override
public ClientsApi clients(@PathParam("version") String version) {
public ClientsApi clients(String version) {
return switch (version) {
case "v2" -> new DefaultClientsApi(session, realm, permissions, realmAdminResource);
default -> throw new NotFoundException();
@@ -1,10 +1,12 @@
package org.keycloak.admin.api;
package org.keycloak.rest.admin.api;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import org.keycloak.admin.api.AdminApi;
import org.keycloak.admin.api.AdminRootV2;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resources.admin.AdminCorsPreflightService;
@@ -1,4 +1,4 @@
package org.keycloak.admin.api.client;
package org.keycloak.rest.admin.api.client;
import java.io.InputStream;
@@ -12,6 +12,7 @@ import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.api.client.ClientApi;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
@@ -1,4 +1,4 @@
package org.keycloak.admin.api.client;
package org.keycloak.rest.admin.api.client;
import java.util.stream.Stream;
@@ -10,6 +10,8 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.api.client.ClientApi;
import org.keycloak.admin.api.client.ClientsApi;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
@@ -4,6 +4,8 @@ import java.util.Optional;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.PatchTypeNames;
public enum PatchType {
JSON_MERGE(PatchTypeNames.JSON_MERGE);
@@ -59,7 +59,7 @@ import com.fasterxml.jackson.databind.ObjectReader;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;
import static org.keycloak.representations.admin.v2.validation.ClientSecretNotBlankValidator.isClientSecret;
import static org.keycloak.representations.admin.v2.validators.ClientSecretNotBlankValidator.isClientSecret;
/**
* Legacy implementation of ClientService for Admin API v2 that uses Admin API v1 under hood.
@@ -41,7 +41,7 @@ import org.keycloak.validation.jakarta.HibernateValidatorProvider;
import org.keycloak.validation.jakarta.JakartaValidatorProvider;
import org.keycloak.validation.jakarta.ValidationContext;
import static org.keycloak.representations.admin.v2.validation.ClientSecretNotBlankValidator.isClientSecret;
import static org.keycloak.representations.admin.v2.validators.ClientSecretNotBlankValidator.isClientSecret;
import static org.keycloak.utils.StringUtil.isBlank;
/**
@@ -0,0 +1,4 @@
org.keycloak.representations.admin.v2.validators.ClientSecretNotBlankValidator
org.keycloak.representations.admin.v2.validators.ProtocolUnmodifiedValidator
org.keycloak.representations.admin.v2.validators.UuidUnmodifiedValidator
org.keycloak.representations.admin.v2.validators.ValidRedirectUrisValidator
@@ -22,6 +22,7 @@ import java.util.List;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.Profile;
import org.keycloak.events.admin.OperationType;
@@ -29,7 +30,6 @@ import org.keycloak.representations.admin.v2.OIDCClientRepresentation;
import org.keycloak.representations.admin.v2.SAMLClientRepresentation;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.services.PatchTypeNames;
import org.keycloak.testframework.annotations.InjectHttpClient;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@@ -10,6 +10,7 @@ import java.util.Set;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.authorization.fgap.AdminPermissionsSchema;
import org.keycloak.common.Profile;
@@ -21,7 +22,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.authorization.Logic;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.services.PatchTypeNames;
import org.keycloak.services.client.ClientServiceHelper;
import org.keycloak.testframework.admin.AdminClientFactory;
import org.keycloak.testframework.annotations.InjectAdminClientFactory;
@@ -27,6 +27,7 @@ import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.wrapper.Clients;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
@@ -36,7 +37,6 @@ import org.keycloak.common.Profile;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.representations.admin.v2.OIDCClientRepresentation;
import org.keycloak.representations.admin.v2.SAMLClientRepresentation;
import org.keycloak.services.PatchTypeNames;
import org.keycloak.services.error.ViolationExceptionResponse;
import org.keycloak.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.annotations.InjectClient;
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
@@ -40,7 +41,6 @@ import org.keycloak.representations.idm.ClientPolicyExecutorRepresentation;
import org.keycloak.representations.idm.ClientPolicyRepresentation;
import org.keycloak.representations.idm.ClientProfileRepresentation;
import org.keycloak.representations.idm.ClientProfilesRepresentation;
import org.keycloak.services.PatchTypeNames;
import org.keycloak.services.client.ClientServiceHelper;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
@@ -1,8 +1,8 @@
package org.keycloak.tests.admin.client.v2.validation;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.representations.admin.v2.OIDCClientRepresentation;
import org.keycloak.representations.admin.v2.SAMLClientRepresentation;
import org.keycloak.services.PatchTypeNames;
import org.keycloak.services.error.ViolationExceptionResponse;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@@ -1,6 +1,6 @@
package org.keycloak.tests.admin.client.v2.validation;
import org.keycloak.representations.admin.v2.validation.ValidRedirectUrisValidator;
import org.keycloak.representations.admin.v2.validators.ValidRedirectUrisValidator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -17,49 +17,8 @@
package org.keycloak.services.resources;
/**
* Class of constants relating to the OpenAPI annotations in Keycloak and the Keycloak Admin REST API
*/
public class KeycloakOpenAPI {
public class KeycloakOpenAPI extends org.keycloak.common.constants.KeycloakOpenAPI {
private KeycloakOpenAPI() { }
public static class Profiles {
public static final String ADMIN = "x-smallrye-profile-admin";
private Profiles() { }
}
public static class Admin {
private Admin() { }
public static class Tags {
public static final String ATTACK_DETECTION = "Attack Detection";
public static final String AUTHENTICATION_MANAGEMENT = "Authentication Management";
public static final String CLIENTS = "Clients";
public static final String CLIENTS_V2 = "Clients (v2)";
public static final String CLIENT_ATTRIBUTE_CERTIFICATE = "Client Attribute Certificate";
public static final String CLIENT_INITIAL_ACCESS = "Client Initial Access";
public static final String CLIENT_REGISTRATION_POLICY = "Client Registration Policy";
public static final String CLIENT_ROLE_MAPPINGS = "Client Role Mappings";
public static final String CLIENT_SCOPES = "Client Scopes";
public static final String COMPONENT = "Component";
public static final String GROUPS = "Groups";
public static final String IDENTITY_PROVIDERS = "Identity Providers";
public static final String KEY = "Key";
public static final String PROTOCOL_MAPPERS = "Protocol Mappers";
public static final String REALMS_ADMIN = "Realms Admin";
public static final String ROLES = "Roles";
public static final String ROLES_BY_ID = "Roles (by ID)";
public static final String ROLE_MAPPER = "Role Mapper";
public static final String ROOT = "Root";
public static final String SCOPE_MAPPINGS = "Scope Mappings";
public static final String USERS = "Users";
public static final String ORGANIZATIONS = "Organizations";
public static final String WORKFLOWS = "Workflows";
private Tags() { }
}
}
}