Added ClientType implementation from Marek's prototype
Signed-off-by: vibrown <vibrown@redhat.com> More updates Signed-off-by: vibrown <vibrown@redhat.com> Added client type logic from Marek's prototype Signed-off-by: vibrown <vibrown@redhat.com> updates Signed-off-by: vibrown <vibrown@redhat.com> updates Signed-off-by: vibrown <vibrown@redhat.com> updates Signed-off-by: vibrown <vibrown@redhat.com> Testing to see if skipRestart was cause of test failures in MR
This commit is contained in:
parent
9c1790af68
commit
3fffc5182e
20 changed files with 664 additions and 122 deletions
|
@ -31,6 +31,7 @@ public class ClientRepresentation {
|
|||
protected String clientId;
|
||||
protected String name;
|
||||
protected String description;
|
||||
protected String type;
|
||||
protected String rootUrl;
|
||||
protected String adminUrl;
|
||||
protected String baseUrl;
|
||||
|
@ -105,6 +106,14 @@ public class ClientRepresentation {
|
|||
this.description = description;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
@ -61,17 +60,6 @@ public class ClientTypeRepresentation {
|
|||
this.config = config;
|
||||
}
|
||||
|
||||
@JsonProperty("referenced-properties")
|
||||
protected Map<String, Object> referencedProperties = new HashMap<>();
|
||||
|
||||
public Map<String, Object> getReferencedProperties() {
|
||||
return referencedProperties;
|
||||
}
|
||||
|
||||
public void setReferencedProperties(Map<String, Object> referencedProperties) {
|
||||
this.referencedProperties = referencedProperties;
|
||||
}
|
||||
|
||||
public static class PropertyConfig {
|
||||
|
||||
@JsonProperty("applicable")
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2021 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.admin.client.resource;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.representations.idm.ClientTypesRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface ClientTypesResource {
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ClientTypesRepresentation getClientTypes();
|
||||
|
||||
|
||||
/**
|
||||
* Update client types in the realm. The "global-client-types" field of client types is ignored as it is not possible to update global types
|
||||
*
|
||||
* @param clientTypes
|
||||
*/
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void updateClientTypes(final ClientTypesRepresentation clientTypes);
|
||||
}
|
|
@ -292,4 +292,7 @@ public interface RealmResource {
|
|||
|
||||
@Path("organizations")
|
||||
OrganizationsResource organizations();
|
||||
|
||||
@Path("client-types")
|
||||
ClientTypesResource clientTypes();
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
|
@ -1195,7 +1197,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
if (invalidations.contains(delegate.getId())) return delegate;
|
||||
StorageId storageId = new StorageId(delegate.getId());
|
||||
CachedClient cached = null;
|
||||
ClientAdapter adapter = null;
|
||||
ClientModel adapter = null;
|
||||
|
||||
if (!storageId.isLocal()) {
|
||||
ComponentModel component = realm.getComponent(storageId.getProviderId());
|
||||
|
@ -1209,7 +1211,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
|
||||
cached = new CachedClient(revision, realm, delegate);
|
||||
adapter = new ClientAdapter(realm, cached, this);
|
||||
adapter = toClientModel(realm, cached);
|
||||
|
||||
long lifespan = model.getLifespan();
|
||||
if (lifespan > 0) {
|
||||
|
@ -1219,7 +1221,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
} else {
|
||||
cached = new CachedClient(revision, realm, delegate);
|
||||
adapter = new ClientAdapter(realm, cached, this);
|
||||
adapter = toClientModel(realm, cached);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
}
|
||||
|
||||
|
@ -1247,9 +1249,17 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return getClientDelegate().getClientById(realm, cached.getId());
|
||||
}
|
||||
}
|
||||
ClientAdapter adapter = new ClientAdapter(realm, cached, this);
|
||||
return toClientModel(realm, cached);
|
||||
}
|
||||
|
||||
return adapter;
|
||||
private ClientModel toClientModel(RealmModel realm, CachedClient cached) {
|
||||
ClientAdapter client = new ClientAdapter(realm, cached, this);
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES)) {
|
||||
ClientTypeManager mgr = session.getProvider(ClientTypeManager.class);
|
||||
return mgr.augmentClient(client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,6 +43,8 @@ import java.util.stream.Collectors;
|
|||
import java.util.stream.Stream;
|
||||
import org.hibernate.Session;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
|
@ -290,13 +292,14 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
TypedQuery<Map> query = em.createNamedQuery("getAllRedirectUrisOfEnabledClients", Map.class);
|
||||
query.setParameter("realm", realm.getId());
|
||||
return closing(query.getResultStream()
|
||||
.filter(s -> s.get("client") != null))
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
s -> new ClientAdapter(realm, em, session, (ClientEntity) s.get("client")),
|
||||
Collectors.mapping(s -> (String) s.get("redirectUri"), Collectors.toSet())
|
||||
)
|
||||
);
|
||||
.filter(s -> s.get("client") != null))
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
s -> toClientModel(realm, (ClientEntity) s.get("client")),
|
||||
Collectors.mapping(s -> (String) s.get("redirectUri"), Collectors.toSet())
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -755,6 +758,8 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
|
||||
@Override
|
||||
public ClientModel addClient(RealmModel realm, String id, String clientId) {
|
||||
ClientModel resource;
|
||||
|
||||
if (id == null) {
|
||||
id = KeycloakModelUtils.generateId();
|
||||
}
|
||||
|
@ -773,7 +778,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
entity.setRealmId(realm.getId());
|
||||
em.persist(entity);
|
||||
|
||||
final ClientModel resource = new ClientAdapter(realm, em, session, entity);
|
||||
resource = toClientModel(realm, entity);
|
||||
|
||||
session.getKeycloakSessionFactory().publish((ClientModel.ClientCreationEvent) () -> resource);
|
||||
return resource;
|
||||
|
@ -810,9 +815,18 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
ClientEntity client = em.find(ClientEntity.class, id);
|
||||
// Check if client belongs to this realm
|
||||
if (client == null || !realm.getId().equals(client.getRealmId())) return null;
|
||||
ClientAdapter adapter = new ClientAdapter(realm, em, session, client);
|
||||
return adapter;
|
||||
return toClientModel(realm, client);
|
||||
}
|
||||
|
||||
private ClientModel toClientModel(RealmModel realm, ClientEntity client) {
|
||||
ClientAdapter adapter = new ClientAdapter(realm, em, session, client);
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES)) {
|
||||
ClientTypeManager mgr = session.getProvider(ClientTypeManager.class);
|
||||
return mgr.augmentClient(adapter);
|
||||
} else {
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,7 +34,7 @@ public class ClientTypeSpi implements Spi {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "client-type";
|
||||
return "clientType";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -119,6 +119,11 @@ public class ModelToRepresentation {
|
|||
REALM_EXCLUDED_ATTRIBUTES.add("firstBrokerLoginFlowId");
|
||||
}
|
||||
|
||||
public static Set<String> CLIENT_EXCLUDED_ATTRIBUTES = new HashSet<>();
|
||||
static {
|
||||
CLIENT_EXCLUDED_ATTRIBUTES.add(ClientModel.TYPE);
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ModelToRepresentation.class);
|
||||
|
||||
public static String buildGroupPath(GroupModel group) {
|
||||
|
@ -536,6 +541,20 @@ public class ModelToRepresentation {
|
|||
|
||||
return a;
|
||||
}
|
||||
|
||||
public static Map<String, String> stripClientAttributesIncludedAsFields(Map<String, String> attributes) {
|
||||
Map<String, String> a = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, String> e : attributes.entrySet()) {
|
||||
if (CLIENT_EXCLUDED_ATTRIBUTES.contains(e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
a.put(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
public static void exportGroups(KeycloakSession session, RealmModel realm, RealmRepresentation rep) {
|
||||
rep.setGroups(toGroupHierarchy(session, realm, true).collect(Collectors.toList()));
|
||||
}
|
||||
|
@ -686,13 +705,14 @@ public class ModelToRepresentation {
|
|||
rep.setClientId(clientModel.getClientId());
|
||||
rep.setName(clientModel.getName());
|
||||
rep.setDescription(clientModel.getDescription());
|
||||
rep.setType(clientModel.getType());
|
||||
rep.setEnabled(clientModel.isEnabled());
|
||||
rep.setAlwaysDisplayInConsole(clientModel.isAlwaysDisplayInConsole());
|
||||
rep.setAdminUrl(clientModel.getManagementUrl());
|
||||
rep.setPublicClient(clientModel.isPublicClient());
|
||||
rep.setFrontchannelLogout(clientModel.isFrontchannelLogout());
|
||||
rep.setProtocol(clientModel.getProtocol());
|
||||
rep.setAttributes(clientModel.getAttributes());
|
||||
rep.setAttributes(stripClientAttributesIncludedAsFields(clientModel.getAttributes()));
|
||||
rep.setAuthenticationFlowBindingOverrides(clientModel.getAuthenticationFlowBindingOverrides());
|
||||
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
|
||||
rep.setBearerOnly(clientModel.isBearerOnly());
|
||||
|
|
|
@ -319,6 +319,7 @@ public class RepresentationToModel {
|
|||
ClientModel client = resourceRep.getId() != null ? realm.addClient(resourceRep.getId(), resourceRep.getClientId()) : realm.addClient(resourceRep.getClientId());
|
||||
if (resourceRep.getName() != null) client.setName(resourceRep.getName());
|
||||
if (resourceRep.getDescription() != null) client.setDescription(resourceRep.getDescription());
|
||||
if (resourceRep.getType() != null) client.setType(resourceRep.getType());
|
||||
if (resourceRep.isEnabled() != null) client.setEnabled(resourceRep.isEnabled());
|
||||
if (resourceRep.isAlwaysDisplayInConsole() != null) client.setAlwaysDisplayInConsole(resourceRep.isAlwaysDisplayInConsole());
|
||||
client.setManagementUrl(resourceRep.getAdminUrl());
|
||||
|
@ -493,6 +494,7 @@ public class RepresentationToModel {
|
|||
if (newClientId != null) resource.setClientId(newClientId);
|
||||
if (rep.getName() != null) resource.setName(rep.getName());
|
||||
if (rep.getDescription() != null) resource.setDescription(rep.getDescription());
|
||||
if (rep.getType() != null) resource.setType(rep.getType());
|
||||
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
|
||||
if (rep.isAlwaysDisplayInConsole() != null) resource.setAlwaysDisplayInConsole(rep.isAlwaysDisplayInConsole());
|
||||
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
|
||||
|
|
|
@ -39,6 +39,7 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
|||
String LOGO_URI ="logoUri";
|
||||
String POLICY_URI ="policyUri";
|
||||
String TOS_URI ="tosUri";
|
||||
String TYPE = "type";
|
||||
|
||||
interface ClientCreationEvent extends ProviderEvent {
|
||||
ClientModel getCreatedClient();
|
||||
|
@ -105,6 +106,14 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
|||
|
||||
void setDescription(String description);
|
||||
|
||||
default String getType() {
|
||||
return getAttribute(TYPE);
|
||||
}
|
||||
|
||||
default void setType(String type) {
|
||||
setAttribute(TYPE, type);
|
||||
}
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
void setEnabled(boolean enabled);
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||
import org.keycloak.representations.idm.ClientTypesRepresentation;
|
||||
import org.keycloak.services.clienttype.client.TypeAwareClientModelDelegate;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
|
@ -66,6 +67,7 @@ public class DefaultClientTypeManager implements ClientTypeManager {
|
|||
try {
|
||||
// Skip validation here for performance reasons
|
||||
result = JsonSerialization.readValue(asStr, ClientTypesRepresentation.class);
|
||||
result.setGlobalClientTypes(globalClientTypes);
|
||||
} catch (IOException ioe) {
|
||||
throw new ClientTypeException("Failed to deserialize client types from JSON string", ioe);
|
||||
}
|
||||
|
@ -104,14 +106,12 @@ public class DefaultClientTypeManager implements ClientTypeManager {
|
|||
|
||||
@Override
|
||||
public ClientModel augmentClient(ClientModel client) throws ClientTypeException {
|
||||
//TODO:vibrown put the logic back in next Client Type PR
|
||||
return client;
|
||||
/*if (client.getType() == null) {
|
||||
if (client.getType() == null) {
|
||||
return client;
|
||||
} else {
|
||||
ClientType clientType = getClientType(client.getRealm(), client.getType());
|
||||
return new TypeAwareClientModelDelegate(clientType, () -> client);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
static List<ClientTypeRepresentation> validateAndCastConfiguration(KeycloakSession session, List<ClientTypeRepresentation> clientTypes, List<ClientTypeRepresentation> globalTypes) {
|
||||
|
@ -129,8 +129,8 @@ public class DefaultClientTypeManager implements ClientTypeManager {
|
|||
private static ClientTypeRepresentation validateAndCastConfiguration(KeycloakSession session, ClientTypeRepresentation clientType, Set<String> currentNames) {
|
||||
ClientTypeProvider clientTypeProvider = session.getProvider(ClientTypeProvider.class, clientType.getProvider());
|
||||
if (clientTypeProvider == null) {
|
||||
logger.errorf("Did not found client type provider '%s' for the client type '%s'", clientType.getProvider(), clientType.getName());
|
||||
throw new ClientTypeException("Did not found client type provider");
|
||||
logger.errorf("Did not find client type provider '%s' for the client type '%s'", clientType.getProvider(), clientType.getName());
|
||||
throw new ClientTypeException("Did not find client type provider");
|
||||
}
|
||||
|
||||
// Validate name is not duplicated
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2021 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.services.clienttype.client;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.delegate.ClientModelLazyDelegate;
|
||||
import org.keycloak.client.clienttype.ClientType;
|
||||
import org.keycloak.client.clienttype.ClientTypeException;
|
||||
|
||||
/**
|
||||
* Delegates to client-type and underlying delegate
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class TypeAwareClientModelDelegate extends ClientModelLazyDelegate {
|
||||
|
||||
private final ClientType clientType;
|
||||
|
||||
public TypeAwareClientModelDelegate(ClientType clientType, Supplier<ClientModel> clientModelSupplier) {
|
||||
super(clientModelSupplier);
|
||||
|
||||
if (clientType == null) {
|
||||
throw new IllegalArgumentException("Null client type not supported for client " + getClientId());
|
||||
}
|
||||
this.clientType = clientType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStandardFlowEnabled() {
|
||||
return getBooleanProperty("standardFlowEnabled", super::isStandardFlowEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
|
||||
setBooleanProperty("standardFlowEnabled", standardFlowEnabled, super::setStandardFlowEnabled);
|
||||
}
|
||||
|
||||
|
||||
protected boolean getBooleanProperty(String propertyName, Supplier<Boolean> clientGetter) {
|
||||
// Check if clientType supports the feature. If not, simply return false
|
||||
if (!clientType.isApplicable(propertyName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this is read-only. If yes, then we just directly delegate to return stuff from the clientType rather than from client
|
||||
if (clientType.isReadOnly(propertyName)) {
|
||||
return clientType.getDefaultValue(propertyName, Boolean.class);
|
||||
}
|
||||
|
||||
// Delegate to clientGetter
|
||||
return clientGetter.get();
|
||||
}
|
||||
|
||||
protected void setBooleanProperty(String propertyName, Boolean newValue, Consumer<Boolean> clientSetter) {
|
||||
// Check if clientType supports the feature. If not, return directly
|
||||
if (!clientType.isApplicable(propertyName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is read-only. If yes and there is an attempt to change some stuff, then throw an exception
|
||||
if (clientType.isReadOnly(propertyName)) {
|
||||
Boolean oldVal = clientType.getDefaultValue(propertyName, Boolean.class);
|
||||
if (!ObjectUtil.isEqualOrBothNull(oldVal, newValue)) {
|
||||
throw new ClientTypeException("Property " + propertyName + " of client " + getClientId() + " is read-only due to client type " + clientType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Call clientSetter
|
||||
clientSetter.accept(newValue);
|
||||
}
|
||||
}
|
|
@ -20,11 +20,9 @@ package org.keycloak.services.clienttype.impl;
|
|||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -34,7 +32,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||
import org.keycloak.client.clienttype.ClientType;
|
||||
import org.keycloak.client.clienttype.ClientTypeException;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -43,9 +40,6 @@ public class DefaultClientType implements ClientType {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(DefaultClientType.class);
|
||||
|
||||
// Will be used as reference in JSON. Probably just temporary solution
|
||||
private static final String REFERENCE_PREFIX = "ref::";
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ClientTypeRepresentation clientType;
|
||||
|
||||
|
@ -93,40 +87,9 @@ public class DefaultClientType implements ClientType {
|
|||
if (propertyConfig.getDefaultValue() != null) {
|
||||
if (clientRepresentationProperties.containsKey(property.getKey())) {
|
||||
// Java property on client representation
|
||||
Method setter = clientRepresentationProperties.get(property.getKey()).getWriteMethod();
|
||||
try {
|
||||
PropertyDescriptor propertyDescriptor = clientRepresentationProperties.get(property.getKey());
|
||||
Method setter = propertyDescriptor.getWriteMethod();
|
||||
Object defaultVal = propertyConfig.getDefaultValue();
|
||||
if (defaultVal instanceof String && defaultVal.toString().startsWith(REFERENCE_PREFIX)) {
|
||||
// TODO:client-types re-verify or remove support for "ref::" entirely from the codebase
|
||||
throw new UnsupportedOperationException("Not supported to use ref:: references");
|
||||
// Reference. We need to found referred value and call the setter with it
|
||||
// String referredPropertyName = defaultVal.toString().substring(REFERENCE_PREFIX.length());
|
||||
// Object referredPropertyVal = clientType.getReferencedProperties().get(referredPropertyName);
|
||||
// if (referredPropertyVal == null) {
|
||||
// logger.warnf("Reference '%s' not found used in property '%s' of client type '%s'", defaultVal.toString(), property.getKey(), clientType.getName());
|
||||
// throw new ClientTypeException("Cannot set property on client");
|
||||
// }
|
||||
//
|
||||
// // Generic collections
|
||||
// Type genericType = setter.getGenericParameterTypes()[0];
|
||||
// JavaType jacksonType = JsonSerialization.mapper.constructType(genericType);
|
||||
// Object converted = JsonSerialization.mapper.convertValue(referredPropertyVal, jacksonType);
|
||||
//
|
||||
// setter.invoke(createdClient, converted);
|
||||
} else {
|
||||
Type genericType = setter.getGenericParameterTypes()[0];
|
||||
|
||||
Object converted;
|
||||
if (!defaultVal.getClass().equals(genericType)) {
|
||||
JavaType jacksonType = JsonSerialization.mapper.constructType(genericType);
|
||||
converted = JsonSerialization.mapper.convertValue(defaultVal, jacksonType);
|
||||
} else {
|
||||
converted = defaultVal;
|
||||
}
|
||||
|
||||
setter.invoke(createdClient, converted);
|
||||
}
|
||||
setter.invoke(createdClient, propertyConfig.getDefaultValue());
|
||||
} catch (Exception e) {
|
||||
logger.warnf("Cannot set property '%s' on client with value '%s'. Check configuration of the client type '%s'", property.getKey(), propertyConfig.getDefaultValue(), clientType.getName());
|
||||
throw new ClientTypeException("Cannot set property on client", e);
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authentication.ClientAuthenticator;
|
||||
import org.keycloak.authentication.ClientAuthenticatorFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -41,6 +42,8 @@ import org.keycloak.protocol.saml.SamlProtocol;
|
|||
import org.keycloak.representations.adapters.config.BaseRealmConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.client.clienttype.ClientType;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -79,6 +82,12 @@ public class ClientManager {
|
|||
* @return
|
||||
*/
|
||||
public static ClientModel createClient(KeycloakSession session, RealmModel realm, ClientRepresentation rep) {
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES) && rep.getType() != null) {
|
||||
ClientTypeManager mgr = session.getProvider(ClientTypeManager.class);
|
||||
ClientType clientType = mgr.getClientType(realm, rep.getType());
|
||||
clientType.onCreate(rep);
|
||||
}
|
||||
|
||||
ClientModel client = RepresentationToModel.createClient(session, realm, rep);
|
||||
|
||||
if (rep.getProtocol() != null) {
|
||||
|
@ -164,7 +173,13 @@ public class ClientManager {
|
|||
user.setServiceAccountClientLink(client.getId());
|
||||
}
|
||||
|
||||
// Add protocol mappers to retrieve clientId in access token
|
||||
// Add protocol mappers to retrieve clientId in access token. Ignore this in case type is filled (protocol mappers can be explicitly specified for particular specific type)
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES) || client.getType() == null) {
|
||||
addServiceAccountProtocolMappers(client);
|
||||
}
|
||||
}
|
||||
|
||||
private void addServiceAccountProtocolMappers(ClientModel client) {
|
||||
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
|
||||
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER, client.getClientId());
|
||||
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER,
|
||||
|
|
|
@ -26,8 +26,12 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authorization.admin.AuthorizationService;
|
||||
import org.keycloak.client.clienttype.ClientType;
|
||||
import org.keycloak.client.clienttype.ClientTypeException;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
|
@ -148,6 +152,17 @@ public class ClientResource {
|
|||
session.setAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED,Boolean.FALSE);
|
||||
session.clientPolicy().triggerOnEvent(new AdminClientUpdateContext(rep, client, auth.adminAuth()));
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES)) {
|
||||
if (!ObjectUtil.isEqualOrBothNull(rep.getType(), client.getType())) {
|
||||
throw new ClientTypeException("Not supported to change client type");
|
||||
}
|
||||
if (rep.getType() != null) {
|
||||
ClientTypeManager mgr = session.getProvider(ClientTypeManager.class);
|
||||
ClientType clientType = mgr.getClientType(realm, rep.getType());
|
||||
clientType.onUpdate(client, rep);
|
||||
}
|
||||
}
|
||||
|
||||
updateClientFromRep(rep, client, session);
|
||||
|
||||
ValidationUtil.validateClient(session, client, false, r -> {
|
||||
|
@ -170,6 +185,8 @@ public class ClientResource {
|
|||
return Response.noContent().build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
throw ErrorResponse.exists("Client already exists");
|
||||
} catch (ClientTypeException cte) {
|
||||
throw ErrorResponse.error(cte.getMessage(), Response.Status.BAD_REQUEST);
|
||||
} catch (ClientPolicyException cpe) {
|
||||
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2021 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.services.resources.admin;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.ClientTypesRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.client.clienttype.ClientTypeException;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
|
||||
public class ClientTypesResource {
|
||||
protected static final Logger logger = Logger.getLogger(ClientTypesResource.class);
|
||||
|
||||
protected final ClientTypeManager manager;
|
||||
protected final RealmModel realm;
|
||||
|
||||
private final AdminPermissionEvaluator auth;
|
||||
|
||||
public ClientTypesResource(ClientTypeManager manager, RealmModel realm, AdminPermissionEvaluator auth) {
|
||||
this.manager = manager;
|
||||
this.auth = auth;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.REALMS_ADMIN)
|
||||
@Operation(summary = "List all client types available in the current realm",
|
||||
description = "This endpoint returns a list of both global and realm level client types and the attributes they set"
|
||||
)
|
||||
public ClientTypesRepresentation getClientTypes() {
|
||||
auth.realm().requireViewRealm();
|
||||
|
||||
try {
|
||||
return manager.getClientTypes(realm);
|
||||
} catch (ClientTypeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
throw new BadRequestException(ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST));
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.REALMS_ADMIN)
|
||||
@Operation(summary = "Update a client type",
|
||||
description = "This endpoint allows you to update a realm level client type"
|
||||
)
|
||||
@APIResponse(responseCode = "204", description = "No Content")
|
||||
public Response updateClientTypes(final ClientTypesRepresentation clientTypes) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
try {
|
||||
manager.updateClientTypes(realm, clientTypes);
|
||||
} catch (ClientTypeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
throw ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST);
|
||||
}
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
|
@ -63,6 +63,7 @@ import org.keycloak.Config;
|
|||
import org.keycloak.KeyPairVerifier;
|
||||
import org.keycloak.authentication.CredentialRegistrator;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.VerificationException;
|
||||
|
@ -1228,4 +1229,11 @@ public class RealmAdminResource {
|
|||
ProfileHelper.requireFeature(Profile.Feature.CLIENT_POLICIES);
|
||||
return new ClientProfilesResource(session, auth);
|
||||
}
|
||||
|
||||
@Path("client-types")
|
||||
public ClientTypesResource getClientTypesResource() {
|
||||
ProfileHelper.requireFeature(Profile.Feature.CLIENT_TYPES);
|
||||
return new ClientTypesResource(session.getProvider(ClientTypeManager.class), realm, auth);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -61,53 +61,6 @@
|
|||
"read-only": true,
|
||||
"default-value": true
|
||||
},
|
||||
"protocolMappers": {
|
||||
"applicable": true,
|
||||
"read-only": true,
|
||||
"default-value": [
|
||||
{
|
||||
"name" : "Client IP Address",
|
||||
"protocol" : "openid-connect",
|
||||
"protocolMapper" : "oidc-usersessionmodel-note-mapper",
|
||||
"consentRequired" : false,
|
||||
"config" : {
|
||||
"user.session.note" : "clientAddress",
|
||||
"id.token.claim" : "true",
|
||||
"access.token.claim" : "true",
|
||||
"claim.name" : "clientAddress",
|
||||
"jsonType.label" : "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name" : "Client Host",
|
||||
"protocol" : "openid-connect",
|
||||
"protocolMapper" : "oidc-usersessionmodel-note-mapper",
|
||||
"consentRequired" : false,
|
||||
"config" : {
|
||||
"user.session.note" : "clientHost",
|
||||
"id.token.claim" : "true",
|
||||
"access.token.claim" : "true",
|
||||
"claim.name" : "clientHost",
|
||||
"jsonType.label" : "String"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"webOrigins": {
|
||||
"applicable": true,
|
||||
"read-only": true,
|
||||
"default-value": [ "https://foo", "https://bar"]
|
||||
},
|
||||
"defaultClientScopes": {
|
||||
"applicable": true,
|
||||
"read-only": true,
|
||||
"default-value": [ "address", "offline_access"]
|
||||
},
|
||||
"optionalClientScopes": {
|
||||
"applicable": true,
|
||||
"read-only": true,
|
||||
"default-value": [ "profile" ]
|
||||
},
|
||||
"logoUri": {
|
||||
"applicable": false
|
||||
},
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* Copyright 2021 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.testsuite.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||
|
||||
import org.keycloak.representations.idm.ClientTypesRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.services.clienttype.impl.DefaultClientTypeProviderFactory;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.common.Profile.Feature.CLIENT_TYPES;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@EnableFeature(value = CLIENT_TYPES)
|
||||
public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeatureWorksWhenEnabled() {
|
||||
checkIfFeatureWorks(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@UncaughtServerErrorExpected
|
||||
@DisableFeature(value = CLIENT_TYPES, skipRestart = true)
|
||||
public void testFeatureDoesntWorkWhenDisabled() {
|
||||
checkIfFeatureWorks(false);
|
||||
}
|
||||
|
||||
// Test create client with clientType filled. Check default properties are filled
|
||||
@Test
|
||||
public void testCreateClientWithClientType() {
|
||||
ClientRepresentation clientRep = createClientWithType("foo", ClientTypeManager.SERVICE_ACCOUNT);
|
||||
Assert.assertEquals("foo", clientRep.getClientId());
|
||||
Assert.assertEquals(ClientTypeManager.SERVICE_ACCOUNT, clientRep.getType());
|
||||
Assert.assertEquals(OIDCLoginProtocol.LOGIN_PROTOCOL, clientRep.getProtocol());
|
||||
Assert.assertFalse(clientRep.isStandardFlowEnabled());
|
||||
Assert.assertFalse(clientRep.isImplicitFlowEnabled());
|
||||
Assert.assertFalse(clientRep.isDirectAccessGrantsEnabled());
|
||||
Assert.assertTrue(clientRep.isServiceAccountsEnabled());
|
||||
Assert.assertFalse(clientRep.isPublicClient());
|
||||
Assert.assertFalse(clientRep.isBearerOnly());
|
||||
|
||||
// Check type not included as client attribute
|
||||
Assert.assertFalse(clientRep.getAttributes().containsKey(ClientModel.TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClientWithClientType() {
|
||||
ClientRepresentation clientRep = createClientWithType("foo", ClientTypeManager.SERVICE_ACCOUNT);
|
||||
|
||||
// Changing type should fail
|
||||
clientRep.setType(ClientTypeManager.STANDARD);
|
||||
try {
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
Assert.fail("Not expected to update client");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Updating read-only attribute should fail
|
||||
clientRep.setType(ClientTypeManager.SERVICE_ACCOUNT);
|
||||
clientRep.setServiceAccountsEnabled(false);
|
||||
try {
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
Assert.fail("Not expected to update client");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Adding non-applicable attribute should fail
|
||||
clientRep.setServiceAccountsEnabled(true);
|
||||
clientRep.getAttributes().put(ClientModel.LOGO_URI, "https://foo");
|
||||
try {
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
Assert.fail("Not expected to update client");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Update of supported attribute should be successful
|
||||
clientRep.getAttributes().remove(ClientModel.LOGO_URI);
|
||||
clientRep.setRootUrl("https://foo");
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClientTypesAdminRestAPI_globalTypes() {
|
||||
ClientTypesRepresentation clientTypes = testRealm().clientTypes().getClientTypes();
|
||||
|
||||
Assert.assertEquals(0, clientTypes.getRealmClientTypes().size());
|
||||
|
||||
List<String> globalClientTypeNames = clientTypes.getGlobalClientTypes().stream()
|
||||
.map(ClientTypeRepresentation::getName)
|
||||
.collect(Collectors.toList());
|
||||
Assert.assertNames(globalClientTypeNames, "sla", "service-account");
|
||||
|
||||
ClientTypeRepresentation serviceAccountType = clientTypes.getGlobalClientTypes().stream()
|
||||
.filter(clientType -> "service-account".equals(clientType.getName()))
|
||||
.findFirst()
|
||||
.get();
|
||||
Assert.assertEquals("default", serviceAccountType.getProvider());
|
||||
|
||||
ClientTypeRepresentation.PropertyConfig cfg = serviceAccountType.getConfig().get("standardFlowEnabled");
|
||||
assertPropertyConfig("standardFlowEnabled", cfg, true, true, false);
|
||||
|
||||
cfg = serviceAccountType.getConfig().get("serviceAccountsEnabled");
|
||||
assertPropertyConfig("serviceAccountsEnabled", cfg, true, true, true);
|
||||
|
||||
cfg = serviceAccountType.getConfig().get("tosUri");
|
||||
assertPropertyConfig("tosUri", cfg, false, null, null);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClientTypesAdminRestAPI_realmTypes() {
|
||||
ClientTypesRepresentation clientTypes = testRealm().clientTypes().getClientTypes();
|
||||
|
||||
// Test invalid provider type should fail
|
||||
ClientTypeRepresentation clientType = new ClientTypeRepresentation();
|
||||
try {
|
||||
clientType.setName("sla1");
|
||||
clientType.setProvider("non-existent");
|
||||
clientType.setConfig(new HashMap<String, ClientTypeRepresentation.PropertyConfig>());
|
||||
clientTypes.setRealmClientTypes(Arrays.asList(clientType));
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
Assert.fail("Not expected to update client types");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Test attribute without applicable should fail
|
||||
try {
|
||||
clientType.setProvider(DefaultClientTypeProviderFactory.PROVIDER_ID);
|
||||
ClientTypeRepresentation.PropertyConfig cfg = new ClientTypeRepresentation.PropertyConfig();
|
||||
clientType.getConfig().put("standardFlowEnabled", cfg);
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
Assert.fail("Not expected to update client types");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Test non-applicable attribute with default-value should fail
|
||||
try {
|
||||
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get("standardFlowEnabled");
|
||||
cfg.setApplicable(false);
|
||||
cfg.setReadOnly(true);
|
||||
cfg.setDefaultValue(true);
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
Assert.fail("Not expected to update client types");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Update should be successful
|
||||
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get("standardFlowEnabled");
|
||||
cfg.setApplicable(true);
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
|
||||
// Test duplicate name should fail
|
||||
ClientTypeRepresentation clientType2 = new ClientTypeRepresentation();
|
||||
try {
|
||||
clientTypes = testRealm().clientTypes().getClientTypes();
|
||||
clientType2 = new ClientTypeRepresentation();
|
||||
clientType2.setName("sla1");
|
||||
clientType2.setProvider(DefaultClientTypeProviderFactory.PROVIDER_ID);
|
||||
clientType2.setConfig(new HashMap<>());
|
||||
clientTypes.getRealmClientTypes().add(clientType2);
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
Assert.fail("Not expected to update client types");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Also test duplicated global name should fail
|
||||
try {
|
||||
clientType2.setName("service-account");
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
Assert.fail("Not expected to update client types");
|
||||
} catch (BadRequestException bre) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Different name should be fine
|
||||
clientType2.setName("different");
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
|
||||
// Assert updated
|
||||
clientTypes = testRealm().clientTypes().getClientTypes();
|
||||
assertNames(clientTypes.getRealmClientTypes(), "sla1", "different");
|
||||
assertNames(clientTypes.getGlobalClientTypes(), "sla", "service-account");
|
||||
|
||||
// Test updating global won't update anything. Nothing will be added to globalTypes
|
||||
clientType2.setName("moreDifferent");
|
||||
clientTypes.getGlobalClientTypes().add(clientType2);
|
||||
testRealm().clientTypes().updateClientTypes(clientTypes);
|
||||
|
||||
clientTypes = testRealm().clientTypes().getClientTypes();
|
||||
assertNames(clientTypes.getRealmClientTypes(), "sla1", "different");
|
||||
assertNames(clientTypes.getGlobalClientTypes(), "sla", "service-account");
|
||||
}
|
||||
|
||||
private void assertNames(List<ClientTypeRepresentation> clientTypes, String... expectedNames) {
|
||||
List<String> names = clientTypes.stream()
|
||||
.map(ClientTypeRepresentation::getName)
|
||||
.collect(Collectors.toList());
|
||||
Assert.assertNames(names, expectedNames);
|
||||
}
|
||||
|
||||
|
||||
private void assertPropertyConfig(String propertyName, ClientTypeRepresentation.PropertyConfig cfg, Boolean expectedApplicable, Boolean expectedReadOnly, Object expectedDefaultValue) {
|
||||
assertThat("'applicable' for property " + propertyName + " not equal", ObjectUtil.isEqualOrBothNull(expectedApplicable, cfg.getApplicable()));
|
||||
assertThat("'read-only' for property " + propertyName + " not equal", ObjectUtil.isEqualOrBothNull(expectedReadOnly, cfg.getReadOnly()));
|
||||
assertThat("'default-value' ;for property " + propertyName + " not equal", ObjectUtil.isEqualOrBothNull(expectedDefaultValue, cfg.getDefaultValue()));
|
||||
}
|
||||
|
||||
private ClientRepresentation createClientWithType(String clientId, String clientType) {
|
||||
ClientRepresentation clientRep = ClientBuilder.create()
|
||||
.clientId(clientId)
|
||||
.type(clientType)
|
||||
.build();
|
||||
Response response = testRealm().clients().create(clientRep);
|
||||
String clientUUID = ApiUtil.getCreatedId(response);
|
||||
getCleanup().addClientUuid(clientUUID);
|
||||
|
||||
return testRealm().clients().get(clientUUID).toRepresentation();
|
||||
}
|
||||
|
||||
// Check if the feature really works
|
||||
private void checkIfFeatureWorks(boolean shouldWork) {
|
||||
try {
|
||||
ClientTypesRepresentation clientTypes = testRealm().clientTypes().getClientTypes();
|
||||
Assert.assertTrue(clientTypes.getRealmClientTypes().isEmpty());
|
||||
if (!shouldWork)
|
||||
fail("Feature is available, but at this moment should be disabled");
|
||||
|
||||
} catch (Exception e) {
|
||||
if (shouldWork) {
|
||||
e.printStackTrace();
|
||||
fail("Feature is not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,6 +63,11 @@ public class ClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder type(String type) {
|
||||
rep.setType(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder consentRequired(boolean consentRequired) {
|
||||
rep.setConsentRequired(consentRequired);
|
||||
return this;
|
||||
|
|
Loading…
Reference in a new issue