Added SPIs for ClientType and ClientTypeManager
Grabbed the SPIs for ClientType and ClientTypeManager from Marek's Client Type prototype. Closes #26431 Signed-off-by: vibrown <vibrown@redhat.com> Cleaned up TODOs Signed-off-by: vibrown <vibrown@redhat.com> Added isSupported methods Signed-off-by: vibrown <vibrown@redhat.com>
This commit is contained in:
parent
bb12f3fb82
commit
161d03efd2
19 changed files with 1289 additions and 0 deletions
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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.representations.idm;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientTypeRepresentation {
|
||||||
|
|
||||||
|
@JsonProperty("name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@JsonProperty("provider")
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
@JsonProperty("config")
|
||||||
|
private Map<String, PropertyConfig> config;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProvider(String provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, PropertyConfig> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(Map<String, PropertyConfig> config) {
|
||||||
|
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")
|
||||||
|
private Boolean applicable;
|
||||||
|
|
||||||
|
@JsonProperty("read-only")
|
||||||
|
private Boolean readOnly;
|
||||||
|
|
||||||
|
@JsonProperty("default-value")
|
||||||
|
private Object defaultValue;
|
||||||
|
|
||||||
|
public Boolean getApplicable() {
|
||||||
|
return applicable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicable(Boolean applicable) {
|
||||||
|
this.applicable = applicable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getReadOnly() {
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadOnly(Boolean readOnly) {
|
||||||
|
this.readOnly = readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultValue(Object defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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.representations.idm;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientTypesRepresentation {
|
||||||
|
|
||||||
|
@JsonProperty("client-types")
|
||||||
|
private List<ClientTypeRepresentation> realmClientTypes;
|
||||||
|
|
||||||
|
@JsonProperty("global-client-types")
|
||||||
|
private List<ClientTypeRepresentation> globalClientTypes;
|
||||||
|
|
||||||
|
public ClientTypesRepresentation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientTypesRepresentation(List<ClientTypeRepresentation> realmClientTypes, List<ClientTypeRepresentation> globalClientTypes) {
|
||||||
|
this.realmClientTypes = realmClientTypes;
|
||||||
|
this.globalClientTypes = globalClientTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClientTypeRepresentation> getRealmClientTypes() {
|
||||||
|
return realmClientTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmClientTypes(List<ClientTypeRepresentation> realmClientTypes) {
|
||||||
|
this.realmClientTypes = realmClientTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClientTypeRepresentation> getGlobalClientTypes() {
|
||||||
|
return globalClientTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGlobalClientTypes(List<ClientTypeRepresentation> globalClientTypes) {
|
||||||
|
this.globalClientTypes = globalClientTypes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO:client-types javadocs
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClientType {
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
// Augment client type at runtime
|
||||||
|
// Can be property name (like "standardFlow" or "rootUrl") or attributeName (like "pkceEnabled")
|
||||||
|
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
|
||||||
|
<T> T getDefaultValue(String optionName, Class<T> optionType);
|
||||||
|
|
||||||
|
|
||||||
|
// Augment at the client type
|
||||||
|
// Augment particular client on creation of client (TODO:client-types Should it be clientModel or clientRepresentation? Or something else?)
|
||||||
|
void onCreate(ClientRepresentation newClient) throws ClientTypeException;
|
||||||
|
|
||||||
|
// Augment particular client on update of client (TODO:client-types Should it be clientModel or clientRepresentation? Or something else?)
|
||||||
|
void onUpdate(ClientModel currentClient, ClientRepresentation clientToUpdate) throws ClientTypeException;
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientTypeException extends ModelException {
|
||||||
|
|
||||||
|
public ClientTypeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientTypeException(String message, Object ... parameters) {
|
||||||
|
super(message, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientTypeException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientTypeException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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.client.clienttype;
|
||||||
|
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.representations.idm.ClientTypesRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO:client-types javadoc
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClientTypeManager extends Provider {
|
||||||
|
|
||||||
|
// Constants for global types
|
||||||
|
String STANDARD = "standard";
|
||||||
|
String SERVICE_ACCOUNT = "service-account";
|
||||||
|
|
||||||
|
// TODO:client-types javadoc
|
||||||
|
ClientTypesRepresentation getClientTypes(RealmModel realm) throws ClientTypeException;
|
||||||
|
|
||||||
|
// Implementation is supposed also to validate clientTypes before persisting them
|
||||||
|
void updateClientTypes(RealmModel realm, ClientTypesRepresentation clientTypes) throws ClientTypeException;
|
||||||
|
|
||||||
|
ClientType getClientType(RealmModel realm, String typeName) throws ClientTypeException;
|
||||||
|
|
||||||
|
// Create client, which delegates to the particular client type
|
||||||
|
ClientModel augmentClient(ClientModel client) throws ClientTypeException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClientTypeManagerFactory extends ProviderFactory<ClientTypeManager>, EnvironmentDependentProviderFactory {
|
||||||
|
}
|
|
@ -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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientTypeManagerSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "client-type-manager";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return ClientTypeManager.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return ClientTypeManagerFactory.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClientTypeProvider extends Provider {
|
||||||
|
|
||||||
|
// Return client types for the model returned
|
||||||
|
ClientType getClientType(ClientTypeRepresentation clientTypeRep);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
ClientTypeRepresentation checkClientTypeConfig(ClientTypeRepresentation clientType) throws ClientTypeException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ClientTypeProviderFactory extends ProviderFactory<ClientTypeProvider> {
|
||||||
|
}
|
|
@ -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.client.clienttype;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientTypeSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "client-type";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return ClientTypeProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return ClientTypeProviderFactory.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
org.keycloak.client.clienttype.ClientTypeManagerSpi
|
||||||
|
org.keycloak.client.clienttype.ClientTypeSpi
|
||||||
org.keycloak.component.ComponentFactorySpi
|
org.keycloak.component.ComponentFactorySpi
|
||||||
org.keycloak.provider.ExceptionConverterSpi
|
org.keycloak.provider.ExceptionConverterSpi
|
||||||
org.keycloak.models.ClientSpi
|
org.keycloak.models.ClientSpi
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.client.clienttype.ClientType;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeException;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeProvider;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
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.util.JsonSerialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class DefaultClientTypeManager implements ClientTypeManager {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultClientTypeManager.class);
|
||||||
|
|
||||||
|
// Realm attribute where are client types saved
|
||||||
|
private static final String CLIENT_TYPE_REALM_ATTRIBUTE = "client-types";
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final List<ClientTypeRepresentation> globalClientTypes;
|
||||||
|
|
||||||
|
public DefaultClientTypeManager(KeycloakSession session, List<ClientTypeRepresentation> globalClientTypes) {
|
||||||
|
this.session = session;
|
||||||
|
this.globalClientTypes = globalClientTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientTypesRepresentation getClientTypes(RealmModel realm) throws ClientTypeException {
|
||||||
|
String asStr = realm.getAttribute(CLIENT_TYPE_REALM_ATTRIBUTE);
|
||||||
|
ClientTypesRepresentation result;
|
||||||
|
if (asStr == null) {
|
||||||
|
result = new ClientTypesRepresentation(new ArrayList<>(), null);
|
||||||
|
result.setGlobalClientTypes(globalClientTypes);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Skip validation here for performance reasons
|
||||||
|
result = JsonSerialization.readValue(asStr, ClientTypesRepresentation.class);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new ClientTypeException("Failed to deserialize client types from JSON string", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateClientTypes(RealmModel realm, ClientTypesRepresentation clientTypes) throws ClientTypeException {
|
||||||
|
// Validate before save
|
||||||
|
List<ClientTypeRepresentation> validatedClientTypes = validateAndCastConfiguration(session, clientTypes.getRealmClientTypes(), globalClientTypes);
|
||||||
|
|
||||||
|
ClientTypesRepresentation noGlobalsCopy = new ClientTypesRepresentation(validatedClientTypes, null);
|
||||||
|
try {
|
||||||
|
String asStr = JsonSerialization.writeValueAsString(noGlobalsCopy);
|
||||||
|
realm.setAttribute(CLIENT_TYPE_REALM_ATTRIBUTE, asStr);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new ClientTypeException("Failed to serialize client types to String", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientType getClientType(RealmModel realm, String typeName) throws ClientTypeException {
|
||||||
|
ClientTypesRepresentation clientTypes = getClientTypes(realm);
|
||||||
|
ClientTypeRepresentation clientType = getClientTypeByName(clientTypes, typeName);
|
||||||
|
if (clientType == null) {
|
||||||
|
logger.errorf("Referenced client type '%s' not found");
|
||||||
|
throw new ClientTypeException("Client type not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientTypeProvider provider = session.getProvider(ClientTypeProvider.class, clientType.getProvider());
|
||||||
|
return provider.getClientType(clientType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
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) {
|
||||||
|
Set<String> usedNames = globalTypes.stream()
|
||||||
|
.map(ClientTypeRepresentation::getName)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
return clientTypes.stream()
|
||||||
|
.map(clientType -> validateAndCastConfiguration(session, clientType, usedNames))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO:client-types some javadoc or comment about how this method works
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate name is not duplicated
|
||||||
|
if (currentNames.contains(clientType.getName())) {
|
||||||
|
logger.errorf("Duplicated client type name '%s'", clientType.getName());
|
||||||
|
throw new ClientTypeException("Duplicated client type name");
|
||||||
|
}
|
||||||
|
|
||||||
|
clientType = clientTypeProvider.checkClientTypeConfig(clientType);
|
||||||
|
currentNames.add(clientType.getName());
|
||||||
|
|
||||||
|
return clientType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ClientTypeRepresentation getClientTypeByName(ClientTypesRepresentation clientTypes, String clientTypeName) {
|
||||||
|
// Search realm clientTypes
|
||||||
|
if (clientTypes.getRealmClientTypes() != null) {
|
||||||
|
for (ClientTypeRepresentation clientType : clientTypes.getRealmClientTypes()) {
|
||||||
|
if (clientTypeName.equals(clientType.getName())) {
|
||||||
|
return clientType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Search global clientTypes
|
||||||
|
if (clientTypes.getGlobalClientTypes() != null) {
|
||||||
|
for (ClientTypeRepresentation clientType : clientTypes.getGlobalClientTypes()) {
|
||||||
|
if (clientTypeName.equals(clientType.getName())) {
|
||||||
|
return clientType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeManagerFactory;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ClientTypesRepresentation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class DefaultClientTypeManagerFactory implements ClientTypeManagerFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultClientTypeManagerFactory.class);
|
||||||
|
|
||||||
|
private volatile List<ClientTypeRepresentation> globalClientTypes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientTypeManager create(KeycloakSession session) {
|
||||||
|
return new DefaultClientTypeManager(session, getGlobalClientTypes(session));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<ClientTypeRepresentation> getGlobalClientTypes(KeycloakSession session) {
|
||||||
|
if (globalClientTypes == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (globalClientTypes == null) {
|
||||||
|
logger.info("Loading global client types");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientTypesRepresentation globalTypesRep = JsonSerialization.readValue(getClass().getResourceAsStream("/keycloak-default-client-types.json"), ClientTypesRepresentation.class);
|
||||||
|
this.globalClientTypes = DefaultClientTypeManager.validateAndCastConfiguration(session, globalTypesRep.getRealmClientTypes(), Collections.emptyList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to deserialize global proposed client types from JSON.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return globalClientTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
private final Map<String, PropertyDescriptor> clientRepresentationProperties;
|
||||||
|
|
||||||
|
public DefaultClientType(KeycloakSession session, ClientTypeRepresentation clientType, Map<String, PropertyDescriptor> clientRepresentationProperties) {
|
||||||
|
this.session = session;
|
||||||
|
this.clientType = clientType;
|
||||||
|
this.clientRepresentationProperties = clientRepresentationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return clientType.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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
|
||||||
|
return (cfg != null && cfg.getApplicable() != null) ? cfg.getApplicable() : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly(String optionName) {
|
||||||
|
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get(optionName);
|
||||||
|
|
||||||
|
// Each property is writable by default if not configured for the particular type
|
||||||
|
return (cfg != null && cfg.getReadOnly() != null) ? cfg.getReadOnly() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T getDefaultValue(String optionName, Class<T> optionType) {
|
||||||
|
ClientTypeRepresentation.PropertyConfig cfg = clientType.getConfig().get(optionName);
|
||||||
|
|
||||||
|
return (cfg != null && cfg.getDefaultValue() != null) ? optionType.cast(cfg.getDefaultValue()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(ClientRepresentation createdClient) throws ClientTypeException {
|
||||||
|
for (Map.Entry<String, ClientTypeRepresentation.PropertyConfig> property : clientType.getConfig().entrySet()) {
|
||||||
|
ClientTypeRepresentation.PropertyConfig propertyConfig = property.getValue();
|
||||||
|
if (!propertyConfig.getApplicable()) continue;
|
||||||
|
if (propertyConfig.getDefaultValue() != null) {
|
||||||
|
if (clientRepresentationProperties.containsKey(property.getKey())) {
|
||||||
|
// Java property on client representation
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Client attribute
|
||||||
|
if (createdClient.getAttributes() == null) {
|
||||||
|
createdClient.setAttributes(new HashMap<>());
|
||||||
|
}
|
||||||
|
createdClient.getAttributes().put(property.getKey(), propertyConfig.getDefaultValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(ClientModel currentClient, ClientRepresentation newClient) throws ClientTypeException{
|
||||||
|
ClientRepresentation oldClient = ModelToRepresentation.toRepresentation(currentClient, session);
|
||||||
|
for (Map.Entry<String, ClientTypeRepresentation.PropertyConfig> property : clientType.getConfig().entrySet()) {
|
||||||
|
String propertyName = property.getKey();
|
||||||
|
ClientTypeRepresentation.PropertyConfig propertyConfig = property.getValue();
|
||||||
|
|
||||||
|
Object oldVal = getClientProperty(oldClient, propertyName);
|
||||||
|
Object newVal = getClientProperty(newClient, propertyName);
|
||||||
|
|
||||||
|
// Validate that read-only client properties were not changed. Also validate that non-applicable properties were not changed.
|
||||||
|
if (!propertyConfig.getApplicable() || propertyConfig.getReadOnly()) {
|
||||||
|
if (!ObjectUtil.isEqualOrBothNull(oldVal, newVal)) {
|
||||||
|
logger.warnf("Cannot change property '%s' of client '%s' . Old value '%s', New value '%s'", propertyName, currentClient.getClientId(), oldVal, newVal);
|
||||||
|
throw new ClientTypeException("Cannot change property of client as it is not allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getClientProperty(ClientRepresentation client, String propertyName) {
|
||||||
|
PropertyDescriptor propertyDescriptor = clientRepresentationProperties.get(propertyName);
|
||||||
|
|
||||||
|
if (propertyDescriptor != null) {
|
||||||
|
// Java property
|
||||||
|
Method getter = propertyDescriptor.getReadMethod();
|
||||||
|
try {
|
||||||
|
return getter.invoke(client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warnf("Cannot read property '%s' on client '%s'. Client type is '%s'", propertyName, client.getClientId(), clientType.getName());
|
||||||
|
throw new ClientTypeException("Cannot read property of client", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Attribute
|
||||||
|
return client.getAttributes() == null ? null : client.getAttributes().get(propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* 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.impl;
|
||||||
|
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.representations.idm.ClientTypeRepresentation;
|
||||||
|
import org.keycloak.client.clienttype.ClientType;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeException;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class DefaultClientTypeProvider implements ClientTypeProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultClientTypeProvider.class);
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final Map<String, PropertyDescriptor> clientRepresentationProperties;
|
||||||
|
|
||||||
|
public DefaultClientTypeProvider(KeycloakSession session, Map<String, PropertyDescriptor> clientRepresentationProperties) {
|
||||||
|
this.session = session;
|
||||||
|
this.clientRepresentationProperties = clientRepresentationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientType getClientType(ClientTypeRepresentation clientTypeRep) {
|
||||||
|
return new DefaultClientType(session, clientTypeRep, clientRepresentationProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientTypeRepresentation checkClientTypeConfig(ClientTypeRepresentation clientType) throws ClientTypeException {
|
||||||
|
Map<String, ClientTypeRepresentation.PropertyConfig> config = clientType.getConfig();
|
||||||
|
for (Map.Entry<String, ClientTypeRepresentation.PropertyConfig> entry : config.entrySet()) {
|
||||||
|
String propertyName = entry.getKey();
|
||||||
|
ClientTypeRepresentation.PropertyConfig propConfig = entry.getValue();
|
||||||
|
|
||||||
|
if (propConfig.getApplicable() == null) {
|
||||||
|
logger.errorf("Property '%s' does not have 'applicable' configured for client type '%s'", propertyName, clientType.getName());
|
||||||
|
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
|
||||||
|
if (!propConfig.getApplicable() && (propConfig.getReadOnly() != null || propConfig.getDefaultValue() != 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());
|
||||||
|
throw new ClientTypeException("Invalid configuration of property on client type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:client-types retype configuration
|
||||||
|
return clientType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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.impl;
|
||||||
|
|
||||||
|
import java.beans.BeanInfo;
|
||||||
|
import java.beans.IntrospectionException;
|
||||||
|
import java.beans.Introspector;
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeProvider;
|
||||||
|
import org.keycloak.client.clienttype.ClientTypeProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class DefaultClientTypeProviderFactory implements ClientTypeProviderFactory, EnvironmentDependentProviderFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "default";
|
||||||
|
|
||||||
|
private Map<String, PropertyDescriptor> clientRepresentationProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientTypeProvider create(KeycloakSession session) {
|
||||||
|
return new DefaultClientTypeProvider(session, clientRepresentationProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
Set<String> filtered = Arrays.stream(new String[] {"attributes", "type"}).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
try {
|
||||||
|
BeanInfo bi = Introspector.getBeanInfo(ClientRepresentation.class);
|
||||||
|
PropertyDescriptor[] pd = bi.getPropertyDescriptors();
|
||||||
|
clientRepresentationProperties = Arrays.stream(pd)
|
||||||
|
.filter(desc -> !filtered.contains(desc.getName()))
|
||||||
|
.filter(desc -> desc.getWriteMethod() != null)
|
||||||
|
.map(desc -> {
|
||||||
|
// Take "is" methods into consideration
|
||||||
|
if (desc.getReadMethod() == null && Boolean.class.equals(desc.getPropertyType())) {
|
||||||
|
String methodName = "is" + desc.getName().substring(0, 1).toUpperCase() + desc.getName().substring(1);
|
||||||
|
try {
|
||||||
|
Method getter = ClientRepresentation.class.getDeclaredMethod(methodName);
|
||||||
|
desc.setReadMethod(getter);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Getter method for property " + desc.getName() + " cannot be found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));
|
||||||
|
} catch (IntrospectionException ie) {
|
||||||
|
throw new IllegalStateException("Introspection of Client representation failed", ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_TYPES);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.services.clienttype.DefaultClientTypeManagerFactory
|
|
@ -0,0 +1,19 @@
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.services.clienttype.impl.DefaultClientTypeProviderFactory
|
123
services/src/main/resources/keycloak-default-client-types.json
Normal file
123
services/src/main/resources/keycloak-default-client-types.json
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
"client-types": [
|
||||||
|
{
|
||||||
|
"name": "sla",
|
||||||
|
"provider": "default",
|
||||||
|
"config": {
|
||||||
|
"standardFlowEnabled": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service-account",
|
||||||
|
"provider": "default",
|
||||||
|
"config": {
|
||||||
|
"alwaysDisplayInConsole": {
|
||||||
|
"applicable": false
|
||||||
|
},
|
||||||
|
"consentRequired": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": false
|
||||||
|
},
|
||||||
|
"login_theme": {
|
||||||
|
"applicable": false
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": "openid-connect"
|
||||||
|
},
|
||||||
|
"publicClient": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": false
|
||||||
|
},
|
||||||
|
"bearerOnly": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": false
|
||||||
|
},
|
||||||
|
"standardFlowEnabled": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": false
|
||||||
|
},
|
||||||
|
"implicitFlowEnabled": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": false
|
||||||
|
},
|
||||||
|
"directAccessGrantsEnabled": {
|
||||||
|
"applicable": true,
|
||||||
|
"read-only": true,
|
||||||
|
"default-value": false
|
||||||
|
},
|
||||||
|
"serviceAccountsEnabled": {
|
||||||
|
"applicable": true,
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"policyUri": {
|
||||||
|
"applicable": false
|
||||||
|
},
|
||||||
|
"tosUri": {
|
||||||
|
"applicable": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue