Client type OIDC base read only defaults (#29706)

closes #29742
closes #29422

Signed-off-by: Patrick Jennings <pajennin@redhat.com>
This commit is contained in:
Patrick Jennings 2024-05-22 03:07:19 -04:00 committed by GitHub
parent 68b2e40b38
commit 84acc953dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 131 additions and 119 deletions

View file

@ -20,6 +20,7 @@ package org.keycloak.common.util;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -89,4 +90,8 @@ public class CollectionUtil {
.filter(searchCollection::contains) .filter(searchCollection::contains)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public static <T> Set<T> collectionToSet(Collection<T> collection) {
return collection == null ? null : new HashSet<>(collection);
}
} }

View file

@ -65,11 +65,8 @@ public class ClientTypeRepresentation {
@JsonProperty("applicable") @JsonProperty("applicable")
private Boolean applicable; private Boolean applicable;
@JsonProperty("read-only") @JsonProperty("value")
private Boolean readOnly; private Object value;
@JsonProperty("default-value")
private Object defaultValue;
public Boolean getApplicable() { public Boolean getApplicable() {
return applicable; return applicable;
@ -79,20 +76,13 @@ public class ClientTypeRepresentation {
this.applicable = applicable; this.applicable = applicable;
} }
public Boolean getReadOnly() {
return readOnly; public Object getValue() {
return value;
} }
public void setReadOnly(Boolean readOnly) { public void setValue(Object value) {
this.readOnly = readOnly; this.value = value;
}
public Object getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
} }
} }
} }

View file

@ -19,9 +19,8 @@
package org.keycloak.client.clienttype; package org.keycloak.client.clienttype;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.representations.idm.ClientTypeRepresentation;
import java.util.Map; import java.util.Set;
/** /**
* TODO:client-types javadocs * TODO:client-types javadocs
@ -36,14 +35,10 @@ public interface ClientType {
// Can be property name (like "standardFlow" or "rootUrl") or attributeName (like "pkceEnabled") // Can be property name (like "standardFlow" or "rootUrl") or attributeName (like "pkceEnabled")
boolean isApplicable(String optionName); boolean isApplicable(String optionName);
// Return if option is configurable by clientType or not...
boolean isReadOnly(String optionName);
// Return the value of particular option (if it can be provided by clientType) or return null if this option is not provided by client type // Return the value of particular option (if it can be provided by clientType) or return null if this option is not provided by client type
<T> T getDefaultValue(String optionName, Class<T> optionType); <T> T getTypeValue(String optionName, Class<T> optionType);
Set<String> getOptionNames();
Map<String, ClientTypeRepresentation.PropertyConfig> getConfiguration();
// Augment at the client type // Augment at the client type
ClientModel augment(ClientModel client); ClientModel augment(ClientModel client);

View file

@ -18,7 +18,6 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -61,6 +60,7 @@ import org.keycloak.client.clienttype.ClientTypeException;
import org.keycloak.client.clienttype.ClientTypeManager; import org.keycloak.client.clienttype.ClientTypeManager;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature; import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
@ -413,7 +413,7 @@ public class RepresentationToModel {
public static void updateClient(ClientRepresentation rep, ClientModel resource, KeycloakSession session) { public static void updateClient(ClientRepresentation rep, ClientModel resource, KeycloakSession session) {
if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES)) { if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES)) {
if (!ObjectUtil.isEqualOrBothNull(rep.getType(), rep.getType())) { if (!ObjectUtil.isEqualOrBothNull(resource.getType(), rep.getType())) {
throw new ClientTypeException("Not supported to change client type"); throw new ClientTypeException("Not supported to change client type");
} }
if (rep.getType() != null) { if (rep.getType() != null) {
@ -526,8 +526,8 @@ public class RepresentationToModel {
// Client Secret // Client Secret
add(updatePropertyAction(client::setSecret, () -> determineNewSecret(client, rep))); add(updatePropertyAction(client::setSecret, () -> determineNewSecret(client, rep)));
// Redirect uris / Web origins // Redirect uris / Web origins
add(updatePropertyAction(client::setRedirectUris, () -> collectionToSet(rep.getRedirectUris()), client::getRedirectUris)); add(updatePropertyAction(client::setRedirectUris, () -> CollectionUtil.collectionToSet(rep.getRedirectUris()), client::getRedirectUris));
add(updatePropertyAction(client::setWebOrigins, () -> collectionToSet(rep.getWebOrigins()), () -> defaultWebOrigins(client))); add(updatePropertyAction(client::setWebOrigins, () -> CollectionUtil.collectionToSet(rep.getWebOrigins()), () -> defaultWebOrigins(client)));
}}; }};
// Extended client attributes // Extended client attributes
@ -689,6 +689,7 @@ public class RepresentationToModel {
* @return {@link Supplier} with results of operation. * @return {@link Supplier} with results of operation.
* @param <T> Type of property. * @param <T> Type of property.
*/ */
@SafeVarargs
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)
@ -1645,12 +1646,6 @@ public class RepresentationToModel {
return toModel(representation, authorization, client); return toModel(representation, authorization, client);
} }
private static <T> Set<T> collectionToSet(Collection<T> collection) {
return Optional.ofNullable(collection)
.map(HashSet::new)
.orElse(null);
}
private static void updateOrganizationBroker(RealmModel realm, IdentityProviderRepresentation representation, KeycloakSession session) { private static void updateOrganizationBroker(RealmModel realm, IdentityProviderRepresentation representation, KeycloakSession session) {
if (!Profile.isFeatureEnabled(Feature.ORGANIZATION)) { if (!Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
return; return;

View file

@ -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, super::isStandardFlowEnabled, Boolean.class); .getClientAttribute(clientType, 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, super::isBearerOnly, Boolean.class); .getClientAttribute(clientType, 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, super::isConsentRequired, Boolean.class); .getClientAttribute(clientType, 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, super::isDirectAccessGrantsEnabled, Boolean.class); .getClientAttribute(clientType, 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, super::isAlwaysDisplayInConsole, Boolean.class); .getClientAttribute(clientType, 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, super::isFrontchannelLogout, Boolean.class); .getClientAttribute(clientType, 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, super::isImplicitFlowEnabled, Boolean.class); .getClientAttribute(clientType, 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, super::isServiceAccountsEnabled, Boolean.class); .getClientAttribute(clientType, 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, super::getProtocol, String.class); .getClientAttribute(clientType, 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, super::isPublicClient, Boolean.class); .getClientAttribute(clientType, 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, super::getWebOrigins, Set.class); .getClientAttribute(clientType, 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, super::getRedirectUris, Set.class); .getClientAttribute(clientType, 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, () -> super.getAttribute(name), String.class); return attribute.getClientAttribute(clientType, String.class);
} else { } else {
return super.getAttribute(name); return super.getAttribute(name);
} }
@ -251,9 +251,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.getConfiguration().entrySet().stream() clientType.getOptionNames().stream()
.map(Map.Entry::getKey) .filter(optionName -> TypedClientExtendedAttribute.getAttributesByName().containsKey(optionName))
.filter(entry -> TypedClientExtendedAttribute.getAttributesByName().containsKey(entry))
.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.

View file

@ -11,7 +11,6 @@ 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
@ -89,7 +88,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, Supplier<T> clientGetter, Class<T> tClass) { default <T> T getClientAttribute(ClientType clientType, Class<T> tClass) {
String propertyName = getPropertyName(); String propertyName = getPropertyName();
Object nonApplicableValue = getNonApplicableValue(); Object nonApplicableValue = getNonApplicableValue();
@ -103,32 +102,25 @@ interface TypedClientAttribute {
} }
} }
// Check if this is read-only. If yes, then we just directly delegate to return stuff from the clientType rather than from client return clientType.getTypeValue(propertyName, tClass);
if (clientType.isReadOnly(propertyName)) {
return clientType.getDefaultValue(propertyName, tClass);
}
// Delegate to clientGetter
return clientGetter.get();
} }
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) {
String propertyName = getPropertyName(); String propertyName = getPropertyName();
Object nonApplicableValue = getNonApplicableValue(); // If feature is set as non-applicable, return directly
// Check if clientType supports the feature. If not, return directly if (!clientType.isApplicable(propertyName)) {
if (!clientType.isApplicable(propertyName) && !Objects.equals(nonApplicableValue, newValue)) { if(!Objects.equals(getNonApplicableValue(), newValue)) {
logger.warnf("Property %s is not-applicable to client type %s and can not be modified.", propertyName, clientType.getName()); logger.warnf("Property %s is not-applicable to client type %s and can not be modified.", propertyName, clientType.getName());
}
return; return;
} }
// Check if this is read-only. If yes and there is an attempt to change some stuff, 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.
if (clientType.isReadOnly(propertyName)) { T oldVal = clientType.getTypeValue(propertyName, tClass);
T oldVal = clientType.getDefaultValue(propertyName, tClass); if (!ObjectUtil.isEqualOrBothNull(oldVal, newValue)) {
if (!ObjectUtil.isEqualOrBothNull(oldVal, newValue)) { throw new ClientTypeException(
throw new ClientTypeException( "Property " + propertyName + " is read-only due to client type " + clientType.getName(),
"Property " + propertyName + " is read-only due to client type " + clientType.getName(), propertyName);
propertyName);
}
} }
// Delegate to clientSetter // Delegate to clientSetter

View file

@ -23,7 +23,8 @@ 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.Map; import java.util.Optional;
import java.util.Set;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -43,34 +44,31 @@ public class DefaultClientType implements ClientType {
@Override @Override
public boolean isApplicable(String optionName) { public boolean isApplicable(String optionName) {
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get(optionName);
// Each property is applicable by default if not configured for the particular client type // Each property is applicable by default if not configured for the particular client type
return (cfg != null && cfg.getApplicable() != null) ? cfg.getApplicable() : true; return getConfiguration(optionName)
.map(ClientTypeRepresentation.PropertyConfig::getApplicable)
.orElse(true);
} }
@Override @Override
public boolean isReadOnly(String optionName) { public <T> T getTypeValue(String optionName, Class<T> optionType) {
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get(optionName);
// Each property is writable by default if not configured for the particular type return getConfiguration(optionName)
return (cfg != null && cfg.getReadOnly() != null) ? cfg.getReadOnly() : false; .map(ClientTypeRepresentation.PropertyConfig::getValue)
.map(optionType::cast).orElse(null);
} }
@Override @Override
public <T> T getDefaultValue(String optionName, Class<T> optionType) { public Set<String> getOptionNames() {
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get(optionName); return clientType.getConfig().keySet();
return (cfg != null && cfg.getDefaultValue() != null) ? optionType.cast(cfg.getDefaultValue()) : null;
}
@Override
public Map<String, ClientTypeRepresentation.PropertyConfig> getConfiguration() {
return clientType.getConfig();
} }
@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));
}
} }

View file

@ -50,8 +50,8 @@ public class DefaultClientTypeProvider implements ClientTypeProvider {
throw new ClientTypeException("Invalid configuration of 'applicable' property on client type"); throw new ClientTypeException("Invalid configuration of 'applicable' property on client type");
} }
// Not supported to set read-only or default-value for properties, which are not applicable for the particular client // Not supported to set value for properties, which are not applicable for the particular client
if (!propConfig.getApplicable() && (propConfig.getReadOnly() != null || propConfig.getDefaultValue() != null)) { if (!propConfig.getApplicable() && propConfig.getValue() != null) {
logger.errorf("Property '%s' is not applicable and so should not have read-only or default-value set for client type '%s'", propertyName, clientType.getName()); logger.errorf("Property '%s' is not applicable and so should not have read-only or default-value set for client type '%s'", propertyName, clientType.getName());
throw new ClientTypeException("Invalid configuration of property on client type"); throw new ClientTypeException("Invalid configuration of property on client type");
} }

View file

@ -6,8 +6,53 @@
"config": { "config": {
"standardFlowEnabled": { "standardFlowEnabled": {
"applicable": true, "applicable": true,
"default-value": true, "value": true
"read-only": true }
}
},
{
"name": "oidc",
"provider": "default",
"config": {
"authorizationServicesEnabled": {
"applicable": true
},
"consentRequired": {
"applicable": true
},
"directAccessGrantsEnabled": {
"applicable": true
},
"frontchannelLogout": {
"applicable": true
},
"fullScopeAllowed": {
"applicable": true
},
"implicitFlowEnabled": {
"applicable": true
},
"nodeReRegistrationTimeout": {
"applicable": true
},
"oauth2.device.authorization.grant.enabled": {
"applicable": true
},
"oidc.ciba.grant.enabled": {
"applicable": true
},
"protocol": {
"applicable": true,
"value": "openid-connect"
},
"publicClient": {
"applicable": true
},
"serviceAccountsEnabled": {
"applicable": true
},
"standardFlowEnabled": {
"applicable": true
} }
} }
}, },
@ -26,8 +71,7 @@
}, },
"consentRequired": { "consentRequired": {
"applicable": true, "applicable": true,
"default-value": false, "value": false
"read-only": true
}, },
"directAccessGrantsEnabled": { "directAccessGrantsEnabled": {
"applicable": false "applicable": false
@ -55,21 +99,18 @@
}, },
"protocol": { "protocol": {
"applicable": true, "applicable": true,
"default-value": "openid-connect", "value": "openid-connect"
"read-only": true
}, },
"publicClient": { "publicClient": {
"applicable": true, "applicable": true,
"default-value": false, "value": false
"read-only": true
}, },
"redirectUris": { "redirectUris": {
"applicable": false "applicable": false
}, },
"serviceAccountsEnabled": { "serviceAccountsEnabled": {
"applicable": true, "applicable": true,
"default-value": true, "value": true
"read-only": true
}, },
"standardFlowEnabled": { "standardFlowEnabled": {
"applicable": false "applicable": false

View file

@ -22,7 +22,6 @@ import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.junit.Test; import org.junit.Test;
import org.keycloak.client.clienttype.ClientTypeManager; import org.keycloak.client.clienttype.ClientTypeManager;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
@ -45,7 +44,9 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.in;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.common.Profile.Feature.CLIENT_TYPES; import static org.keycloak.common.Profile.Feature.CLIENT_TYPES;
@ -154,8 +155,8 @@ public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
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);
assertThat( assertThat(
List.of(errorRepresentation.getParams()), List.of(items),
containsInAnyOrder(items)); everyItem(in(errorRepresentation.getParams())));
} }
@Test @Test
@ -164,10 +165,9 @@ public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
assertEquals(0, clientTypes.getRealmClientTypes().size()); assertEquals(0, clientTypes.getRealmClientTypes().size());
List<String> globalClientTypeNames = clientTypes.getGlobalClientTypes().stream() List<ClientTypeRepresentation> globalClientTypeNames = clientTypes.getGlobalClientTypes().stream()
.map(ClientTypeRepresentation::getName)
.collect(Collectors.toList()); .collect(Collectors.toList());
Assert.assertNames(globalClientTypeNames, "sla", "service-account"); assertNames(globalClientTypeNames, "sla", "service-account");
ClientTypeRepresentation serviceAccountType = clientTypes.getGlobalClientTypes().stream() ClientTypeRepresentation serviceAccountType = clientTypes.getGlobalClientTypes().stream()
.filter(clientType -> "service-account".equals(clientType.getName())) .filter(clientType -> "service-account".equals(clientType.getName()))
@ -176,16 +176,15 @@ public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
assertEquals("default", serviceAccountType.getProvider()); assertEquals("default", serviceAccountType.getProvider());
ClientTypeRepresentation.PropertyConfig cfg = serviceAccountType.getConfig().get("standardFlowEnabled"); ClientTypeRepresentation.PropertyConfig cfg = serviceAccountType.getConfig().get("standardFlowEnabled");
assertPropertyConfig("standardFlowEnabled", cfg, false, null, null); assertPropertyConfig("standardFlowEnabled", cfg, false, null);
cfg = serviceAccountType.getConfig().get("serviceAccountsEnabled"); cfg = serviceAccountType.getConfig().get("serviceAccountsEnabled");
assertPropertyConfig("serviceAccountsEnabled", cfg, true, true, true); assertPropertyConfig("serviceAccountsEnabled", cfg, true, true);
cfg = serviceAccountType.getConfig().get("tosUri"); cfg = serviceAccountType.getConfig().get("tosUri");
assertPropertyConfig("tosUri", cfg, false, null, null); assertPropertyConfig("tosUri", cfg, false, null);
} }
@Test @Test
public void testClientTypesAdminRestAPI_realmTypes() { public void testClientTypesAdminRestAPI_realmTypes() {
ClientTypesRepresentation clientTypes = testRealm().clientTypes().getClientTypes(); ClientTypesRepresentation clientTypes = testRealm().clientTypes().getClientTypes();
@ -218,8 +217,7 @@ public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
try { try {
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get("standardFlowEnabled"); ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get("standardFlowEnabled");
cfg.setApplicable(false); cfg.setApplicable(false);
cfg.setReadOnly(true); cfg.setValue(true);
cfg.setDefaultValue(true);
testRealm().clientTypes().updateClientTypes(clientTypes); testRealm().clientTypes().updateClientTypes(clientTypes);
Assert.fail("Not expected to update client types"); Assert.fail("Not expected to update client types");
} catch (BadRequestException bre) { } catch (BadRequestException bre) {
@ -278,14 +276,13 @@ public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
List<String> names = clientTypes.stream() List<String> names = clientTypes.stream()
.map(ClientTypeRepresentation::getName) .map(ClientTypeRepresentation::getName)
.collect(Collectors.toList()); .collect(Collectors.toList());
Assert.assertNames(names, expectedNames); assertThat(names, hasItems(expectedNames));
} }
private void assertPropertyConfig(String propertyName, ClientTypeRepresentation.PropertyConfig cfg, Boolean expectedApplicable, Boolean expectedReadOnly, Object expectedDefaultValue) { private void assertPropertyConfig(String propertyName, ClientTypeRepresentation.PropertyConfig cfg, Boolean expectedApplicable, Object expectedValue) {
assertThat("'applicable' for property " + propertyName + " not equal", ObjectUtil.isEqualOrBothNull(expectedApplicable, cfg.getApplicable())); assertEquals("'applicable' for property " + propertyName + " not equal", expectedApplicable, cfg.getApplicable());
assertThat("'read-only' for property " + propertyName + " not equal", ObjectUtil.isEqualOrBothNull(expectedReadOnly, cfg.getReadOnly())); assertEquals("'value' for property " + propertyName + " not equal", expectedValue, cfg.getValue());
assertThat("'default-value' ;for property " + propertyName + " not equal", ObjectUtil.isEqualOrBothNull(expectedDefaultValue, cfg.getDefaultValue()));
} }
private ClientRepresentation createClientWithType(String clientId, String clientType) { private ClientRepresentation createClientWithType(String clientId, String clientType) {