Client type configuration inheritance (#30056)
closes #30213 Signed-off-by: Patrick Jennings <pajennin@redhat.com>
This commit is contained in:
parent
7d05a7a013
commit
75925dcf6c
12 changed files with 123 additions and 62 deletions
|
@ -33,6 +33,9 @@ public class ClientTypeRepresentation {
|
||||||
@JsonProperty("provider")
|
@JsonProperty("provider")
|
||||||
private String provider;
|
private String provider;
|
||||||
|
|
||||||
|
@JsonProperty("parent")
|
||||||
|
private String parent;
|
||||||
|
|
||||||
@JsonProperty("config")
|
@JsonProperty("config")
|
||||||
private Map<String, PropertyConfig> config;
|
private Map<String, PropertyConfig> config;
|
||||||
|
|
||||||
|
@ -60,6 +63,14 @@ public class ClientTypeRepresentation {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(String parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
public static class PropertyConfig {
|
public static class PropertyConfig {
|
||||||
|
|
||||||
@JsonProperty("applicable")
|
@JsonProperty("applicable")
|
||||||
|
@ -85,4 +96,4 @@ public class ClientTypeRepresentation {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,4 +42,4 @@ public interface ClientType {
|
||||||
|
|
||||||
// Augment at the client type
|
// Augment at the client type
|
||||||
ClientModel augment(ClientModel client);
|
ClientModel augment(ClientModel client);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,6 @@ public class ClientTypeException extends ModelException {
|
||||||
super(message, parameters);
|
super(message, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientTypeException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Message {
|
public enum Message {
|
||||||
/**
|
/**
|
||||||
* Register all client type exception messages through this enum to keep things consistent across the services.
|
* Register all client type exception messages through this enum to keep things consistent across the services.
|
||||||
|
@ -57,10 +53,6 @@ public class ClientTypeException extends ModelException {
|
||||||
return new ClientTypeException(message, parameters);
|
return new ClientTypeException(message, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientTypeException exception(String message, Throwable cause) {
|
|
||||||
return new ClientTypeException(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||||
public interface ClientTypeProvider extends Provider {
|
public interface ClientTypeProvider extends Provider {
|
||||||
|
|
||||||
// Return client types for the model returned
|
// Return client types for the model returned
|
||||||
ClientType getClientType(ClientTypeRepresentation clientTypeRep);
|
ClientType getClientType(ClientTypeRepresentation clientTypeRep, ClientType parent);
|
||||||
|
|
||||||
// TODO:client-types type-safety here. The returned clientType should have correctly casted client type configuration
|
// TODO:client-types type-safety here. The returned clientType should have correctly casted client type configuration
|
||||||
// Used when creating/updating clientType. The JSON configuration is validated to be checked if it matches the good format for client type
|
// Used when creating/updating clientType. The JSON configuration is validated to be checked if it matches the good format for client type
|
||||||
|
@ -37,4 +37,4 @@ public interface ClientTypeProvider extends Provider {
|
||||||
default void close() {
|
default void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
@ -655,8 +654,8 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create Supplier to update property, if not null.
|
* Create Supplier to update property.
|
||||||
* Captures {@link ClientTypeException} if thrown by the setter.
|
* Captures and returns {@link ClientTypeException} if thrown by the setter.
|
||||||
*
|
*
|
||||||
* @param modelSetter setter to call.
|
* @param modelSetter setter to call.
|
||||||
* @param representationGetter getter supplying the property update.
|
* @param representationGetter getter supplying the property update.
|
||||||
|
@ -687,9 +686,7 @@ public class RepresentationToModel {
|
||||||
private static <T> Supplier<ClientTypeException> updatePropertyAction(Consumer<T> modelSetter, Supplier<T>... getters) {
|
private static <T> Supplier<ClientTypeException> updatePropertyAction(Consumer<T> modelSetter, Supplier<T>... getters) {
|
||||||
Stream<T> firstNonNullSupplied = Stream.of(getters)
|
Stream<T> firstNonNullSupplied = Stream.of(getters)
|
||||||
.map(Supplier::get)
|
.map(Supplier::get)
|
||||||
.map(Optional::ofNullable)
|
.filter(Objects::nonNull);
|
||||||
.filter(Optional::isPresent)
|
|
||||||
.map(Optional::get);
|
|
||||||
return updateProperty(modelSetter, () -> firstNonNullSupplied.findFirst().orElse(null));
|
return updateProperty(modelSetter, () -> firstNonNullSupplied.findFirst().orElse(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ package org.keycloak.services.clienttype;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -102,8 +104,13 @@ public class DefaultClientTypeManager implements ClientTypeManager {
|
||||||
throw ClientTypeException.Message.CLIENT_TYPE_NOT_FOUND.exception();
|
throw ClientTypeException.Message.CLIENT_TYPE_NOT_FOUND.exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClientType parent = null;
|
||||||
|
if (clientType.getParent() != null) {
|
||||||
|
parent = getClientType(realm, clientType.getParent());
|
||||||
|
}
|
||||||
|
|
||||||
ClientTypeProvider provider = session.getProvider(ClientTypeProvider.class, clientType.getProvider());
|
ClientTypeProvider provider = session.getProvider(ClientTypeProvider.class, clientType.getProvider());
|
||||||
return provider.getClientType(clientType);
|
return provider.getClientType(clientType, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -173,4 +180,4 @@ public class DefaultClientTypeManager implements ClientTypeManager {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isStandardFlowEnabled() {
|
public boolean isStandardFlowEnabled() {
|
||||||
return TypedClientSimpleAttribute.STANDARD_FLOW_ENABLED
|
return TypedClientSimpleAttribute.STANDARD_FLOW_ENABLED
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isStandardFlowEnabled, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,7 +61,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isBearerOnly() {
|
public boolean isBearerOnly() {
|
||||||
return TypedClientSimpleAttribute.BEARER_ONLY
|
return TypedClientSimpleAttribute.BEARER_ONLY
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isBearerOnly, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,7 +73,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isConsentRequired() {
|
public boolean isConsentRequired() {
|
||||||
return TypedClientSimpleAttribute.CONSENT_REQUIRED
|
return TypedClientSimpleAttribute.CONSENT_REQUIRED
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isConsentRequired, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -85,7 +85,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectAccessGrantsEnabled() {
|
public boolean isDirectAccessGrantsEnabled() {
|
||||||
return TypedClientSimpleAttribute.DIRECT_ACCESS_GRANTS_ENABLED
|
return TypedClientSimpleAttribute.DIRECT_ACCESS_GRANTS_ENABLED
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isDirectAccessGrantsEnabled, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,7 +97,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAlwaysDisplayInConsole() {
|
public boolean isAlwaysDisplayInConsole() {
|
||||||
return TypedClientSimpleAttribute.ALWAYS_DISPLAY_IN_CONSOLE
|
return TypedClientSimpleAttribute.ALWAYS_DISPLAY_IN_CONSOLE
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isAlwaysDisplayInConsole, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,7 +109,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isFrontchannelLogout() {
|
public boolean isFrontchannelLogout() {
|
||||||
return TypedClientSimpleAttribute.FRONTCHANNEL_LOGOUT
|
return TypedClientSimpleAttribute.FRONTCHANNEL_LOGOUT
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isFrontchannelLogout, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,7 +121,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isImplicitFlowEnabled() {
|
public boolean isImplicitFlowEnabled() {
|
||||||
return TypedClientSimpleAttribute.IMPLICIT_FLOW_ENABLED
|
return TypedClientSimpleAttribute.IMPLICIT_FLOW_ENABLED
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isImplicitFlowEnabled, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -133,7 +133,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isServiceAccountsEnabled() {
|
public boolean isServiceAccountsEnabled() {
|
||||||
return TypedClientSimpleAttribute.SERVICE_ACCOUNTS_ENABLED
|
return TypedClientSimpleAttribute.SERVICE_ACCOUNTS_ENABLED
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isServiceAccountsEnabled, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,7 +145,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public String getProtocol() {
|
public String getProtocol() {
|
||||||
return TypedClientSimpleAttribute.PROTOCOL
|
return TypedClientSimpleAttribute.PROTOCOL
|
||||||
.getClientAttribute(clientType, String.class);
|
.getClientAttribute(clientType, super::getProtocol, String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -157,7 +157,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public boolean isPublicClient() {
|
public boolean isPublicClient() {
|
||||||
return TypedClientSimpleAttribute.PUBLIC_CLIENT
|
return TypedClientSimpleAttribute.PUBLIC_CLIENT
|
||||||
.getClientAttribute(clientType, Boolean.class);
|
.getClientAttribute(clientType, super::isPublicClient, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -169,7 +169,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getWebOrigins() {
|
public Set<String> getWebOrigins() {
|
||||||
return TypedClientSimpleAttribute.WEB_ORIGINS
|
return TypedClientSimpleAttribute.WEB_ORIGINS
|
||||||
.getClientAttribute(clientType, Set.class);
|
.getClientAttribute(clientType, super::getWebOrigins, Set.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -193,7 +193,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getRedirectUris() {
|
public Set<String> getRedirectUris() {
|
||||||
return TypedClientSimpleAttribute.REDIRECT_URIS
|
return TypedClientSimpleAttribute.REDIRECT_URIS
|
||||||
.getClientAttribute(clientType, Set.class);
|
.getClientAttribute(clientType, super::getRedirectUris, Set.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -238,7 +238,7 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
public String getAttribute(String name) {
|
public String getAttribute(String name) {
|
||||||
TypedClientExtendedAttribute attribute = TypedClientExtendedAttribute.getAttributesByName().get(name);
|
TypedClientExtendedAttribute attribute = TypedClientExtendedAttribute.getAttributesByName().get(name);
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
return attribute.getClientAttribute(clientType, String.class);
|
return attribute.getClientAttribute(clientType, () -> super.getAttribute(name), String.class);
|
||||||
} else {
|
} else {
|
||||||
return super.getAttribute(name);
|
return super.getAttribute(name);
|
||||||
}
|
}
|
||||||
|
@ -252,8 +252,8 @@ public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||||
// Get extended client type attributes and values from the client type configuration.
|
// Get extended client type attributes and values from the client type configuration.
|
||||||
Set<String> extendedClientTypeAttributes =
|
Set<String> extendedClientTypeAttributes =
|
||||||
clientType.getOptionNames().stream()
|
clientType.getOptionNames().stream()
|
||||||
.filter(optionName -> TypedClientExtendedAttribute.getAttributesByName().containsKey(optionName))
|
.filter(optionName -> TypedClientExtendedAttribute.getAttributesByName().containsKey(optionName))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// Augment client type attributes on top of attributes on the delegate.
|
// Augment client type attributes on top of attributes on the delegate.
|
||||||
for (String entry : extendedClientTypeAttributes) {
|
for (String entry : extendedClientTypeAttributes) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
enum TypedClientSimpleAttribute implements TypedClientAttribute {
|
enum TypedClientSimpleAttribute implements TypedClientAttribute {
|
||||||
// Top Level client attributes
|
// Top Level client attributes
|
||||||
|
@ -120,7 +121,7 @@ enum TypedClientExtendedAttribute implements TypedClientAttribute {
|
||||||
interface TypedClientAttribute {
|
interface TypedClientAttribute {
|
||||||
Logger logger = Logger.getLogger(TypedClientAttribute.class);
|
Logger logger = Logger.getLogger(TypedClientAttribute.class);
|
||||||
|
|
||||||
default <T> T getClientAttribute(ClientType clientType, Class<T> tClass) {
|
default <T> T getClientAttribute(ClientType clientType, Supplier<T> clientGetter, Class<T> tClass) {
|
||||||
String propertyName = getPropertyName();
|
String propertyName = getPropertyName();
|
||||||
Object nonApplicableValue = getNonApplicableValue();
|
Object nonApplicableValue = getNonApplicableValue();
|
||||||
|
|
||||||
|
@ -134,7 +135,9 @@ interface TypedClientAttribute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientType.getTypeValue(propertyName, tClass);
|
T typeValue = clientType.getTypeValue(propertyName, tClass);
|
||||||
|
// If the value is not supplied by the client type, delegate to the client getter.
|
||||||
|
return typeValue == null ? clientGetter.get() : typeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
default <T> void setClientAttribute(ClientType clientType, T newValue, Consumer<T> clientSetter, Class<T> tClass) {
|
default <T> void setClientAttribute(ClientType clientType, T newValue, Consumer<T> clientSetter, Class<T> tClass) {
|
||||||
|
@ -148,8 +151,8 @@ interface TypedClientAttribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is an attempt to change a value for an applicable field with a read-only value set, then throw an exception.
|
// If there is an attempt to change a value for an applicable field with a read-only value set, then throw an exception.
|
||||||
T oldVal = clientType.getTypeValue(propertyName, tClass);
|
T readOnlyValue = clientType.getTypeValue(propertyName, tClass);
|
||||||
if (!ObjectUtil.isEqualOrBothNull(oldVal, newValue)) {
|
if (readOnlyValue != null && !readOnlyValue.equals(newValue)) {
|
||||||
throw ClientTypeException.Message.CLIENT_UPDATE_FAILED_CLIENT_TYPE_VALIDATION.exception(propertyName);
|
throw ClientTypeException.Message.CLIENT_UPDATE_FAILED_CLIENT_TYPE_VALIDATION.exception(propertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,9 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||||
import org.keycloak.services.clienttype.client.TypeAwareClientModelDelegate;
|
import org.keycloak.services.clienttype.client.TypeAwareClientModelDelegate;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -32,9 +33,11 @@ import java.util.Set;
|
||||||
public class DefaultClientType implements ClientType {
|
public class DefaultClientType implements ClientType {
|
||||||
|
|
||||||
private final ClientTypeRepresentation clientType;
|
private final ClientTypeRepresentation clientType;
|
||||||
|
private final ClientType parentClientType;
|
||||||
|
|
||||||
public DefaultClientType(ClientTypeRepresentation clientType) {
|
public DefaultClientType(ClientTypeRepresentation clientType, ClientType parentClientType) {
|
||||||
this.clientType = clientType;
|
this.clientType = clientType;
|
||||||
|
this.parentClientType = parentClientType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -44,31 +47,40 @@ public class DefaultClientType implements ClientType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isApplicable(String optionName) {
|
public boolean isApplicable(String optionName) {
|
||||||
// Each property is applicable by default if not configured for the particular client type
|
ClientTypeRepresentation.PropertyConfig propertyConfig = clientType.getConfig().get(optionName);
|
||||||
return getConfiguration(optionName)
|
if (propertyConfig != null) {
|
||||||
.map(ClientTypeRepresentation.PropertyConfig::getApplicable)
|
return propertyConfig.getApplicable();
|
||||||
.orElse(true);
|
}
|
||||||
|
|
||||||
|
if (parentClientType != null) {
|
||||||
|
return parentClientType.isApplicable(optionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getTypeValue(String optionName, Class<T> optionType) {
|
public <T> T getTypeValue(String optionName, Class<T> optionType) {
|
||||||
|
ClientTypeRepresentation.PropertyConfig propertyConfig = clientType.getConfig().get(optionName);
|
||||||
return getConfiguration(optionName)
|
if (propertyConfig != null) {
|
||||||
.map(ClientTypeRepresentation.PropertyConfig::getValue)
|
return optionType.cast(propertyConfig.getValue());
|
||||||
.map(optionType::cast).orElse(null);
|
} else if (parentClientType != null) {
|
||||||
|
return parentClientType.getTypeValue(optionName, optionType);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getOptionNames() {
|
public Set<String> getOptionNames() {
|
||||||
return clientType.getConfig().keySet();
|
Stream<String> optionNames = clientType.getConfig().keySet().stream();
|
||||||
|
if (parentClientType != null) {
|
||||||
|
optionNames = Stream.concat(optionNames, parentClientType.getOptionNames().stream());
|
||||||
|
}
|
||||||
|
return optionNames.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientModel augment(ClientModel client) {
|
public ClientModel augment(ClientModel client) {
|
||||||
return new TypeAwareClientModelDelegate(this, () -> client);
|
return new TypeAwareClientModelDelegate(this, () -> client);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private Optional<ClientTypeRepresentation.PropertyConfig> getConfiguration(String optionName) {
|
|
||||||
return Optional.ofNullable(clientType.getConfig().get(optionName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ public class DefaultClientTypeProvider implements ClientTypeProvider {
|
||||||
private static final Logger logger = Logger.getLogger(DefaultClientTypeProvider.class);
|
private static final Logger logger = Logger.getLogger(DefaultClientTypeProvider.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientType getClientType(ClientTypeRepresentation clientTypeRep) {
|
public ClientType getClientType(ClientTypeRepresentation clientTypeRep, ClientType parent) {
|
||||||
return new DefaultClientType(clientTypeRep);
|
return new DefaultClientType(clientTypeRep, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -60,4 +60,4 @@ public class DefaultClientTypeProvider implements ClientTypeProvider {
|
||||||
// TODO:client-types retype configuration
|
// TODO:client-types retype configuration
|
||||||
return clientType;
|
return clientType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
"name": "oidc",
|
"name": "oidc",
|
||||||
"provider": "default",
|
"provider": "default",
|
||||||
"config": {
|
"config": {
|
||||||
|
"alwaysDisplayInConsole": {
|
||||||
|
"applicable": true
|
||||||
|
},
|
||||||
"authorizationServicesEnabled": {
|
"authorizationServicesEnabled": {
|
||||||
"applicable": true
|
"applicable": true
|
||||||
},
|
},
|
||||||
|
@ -59,6 +62,7 @@
|
||||||
{
|
{
|
||||||
"name": "service-account",
|
"name": "service-account",
|
||||||
"provider": "default",
|
"provider": "default",
|
||||||
|
"parent": "oidc",
|
||||||
"config": {
|
"config": {
|
||||||
"alwaysDisplayInConsole": {
|
"alwaysDisplayInConsole": {
|
||||||
"applicable": false
|
"applicable": false
|
||||||
|
@ -97,10 +101,6 @@
|
||||||
"policyUri": {
|
"policyUri": {
|
||||||
"applicable": false
|
"applicable": false
|
||||||
},
|
},
|
||||||
"protocol": {
|
|
||||||
"applicable": true,
|
|
||||||
"value": "openid-connect"
|
|
||||||
},
|
|
||||||
"publicClient": {
|
"publicClient": {
|
||||||
"applicable": true,
|
"applicable": true,
|
||||||
"value": false
|
"value": false
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
@ -266,6 +267,44 @@ public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
|
||||||
assertNames(clientTypes.getGlobalClientTypes(), "sla", "service-account");
|
assertNames(clientTypes.getGlobalClientTypes(), "sla", "service-account");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientTypesInheritFromParent() {
|
||||||
|
ClientTypesRepresentation clientTypes = testRealm().clientTypes().getClientTypes();
|
||||||
|
|
||||||
|
ClientTypeRepresentation.PropertyConfig applicableAndTrue = new ClientTypeRepresentation.PropertyConfig();
|
||||||
|
applicableAndTrue.setApplicable(true);
|
||||||
|
applicableAndTrue.setValue(true);
|
||||||
|
|
||||||
|
ClientTypeRepresentation childClientType = new ClientTypeRepresentation();
|
||||||
|
childClientType.setName("child");
|
||||||
|
childClientType.setProvider("default");
|
||||||
|
childClientType.setParent("oidc");
|
||||||
|
childClientType.setConfig(Map.of("standardFlowEnabled", applicableAndTrue));
|
||||||
|
|
||||||
|
ClientTypeRepresentation subClientType = new ClientTypeRepresentation();
|
||||||
|
subClientType.setName("subClientType");
|
||||||
|
subClientType.setProvider("default");
|
||||||
|
subClientType.setParent("child");
|
||||||
|
subClientType.setConfig(Map.of("consentRequired", applicableAndTrue));
|
||||||
|
|
||||||
|
List<ClientTypeRepresentation> realmClientTypes = clientTypes.getRealmClientTypes();
|
||||||
|
realmClientTypes.add(childClientType);
|
||||||
|
realmClientTypes.add(subClientType);
|
||||||
|
clientTypes.setRealmClientTypes(realmClientTypes);
|
||||||
|
|
||||||
|
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||||
|
|
||||||
|
ClientRepresentation childClient = createClientWithType("child-client", childClientType.getName());
|
||||||
|
assertEquals(childClient.getProtocol(), "openid-connect");
|
||||||
|
assertEquals(childClient.isStandardFlowEnabled(), true);
|
||||||
|
assertEquals(childClient.isConsentRequired(), false);
|
||||||
|
|
||||||
|
ClientRepresentation subClient = createClientWithType("sub-client", subClientType.getName());
|
||||||
|
assertEquals(subClient.getProtocol(), "openid-connect");
|
||||||
|
assertEquals(subClient.isStandardFlowEnabled(), true);
|
||||||
|
assertEquals(subClient.isConsentRequired(), true);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertErrorResponseContainsParams(Response response, String... items) {
|
private void assertErrorResponseContainsParams(Response response, String... items) {
|
||||||
assertEquals(Response.Status.BAD_REQUEST, response.getStatusInfo());
|
assertEquals(Response.Status.BAD_REQUEST, response.getStatusInfo());
|
||||||
ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
|
ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
|
||||||
|
|
Loading…
Reference in a new issue