KEYCLOAK-19374 Create implementation based on annotation processor

Use of boxed types as started in 009d4ca445 is finalized here
to enable storing data in a map. MapClientEntity methods are
reordered for the sake of grouping the collection-based
properties together and understanding the connections between those.
This commit is contained in:
Hynek Mlnarik 2021-09-21 10:16:37 +02:00 committed by Hynek Mlnařík
parent 12d4837fa9
commit 3abf9283a8
17 changed files with 728 additions and 752 deletions

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>keycloak-model-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>16.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-model-build-processor</artifactId>
<name>Keycloak Model Java Annotations and Processor</name>
<description/>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,32 @@
/*
* 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.models.map.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hmlnarik
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateEntityImplementations {
String inherits() default "";
}

View file

@ -0,0 +1,32 @@
/*
* 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.models.map.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hmlnarik
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface GenerateEnumMapFieldType {
Class<?> value() default Void.class;
}

View file

@ -0,0 +1,328 @@
/*
* 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.models.map.processor;
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
import org.keycloak.models.map.annotations.GenerateEnumMapFieldType;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
/**
*
* @author hmlnarik
*/
@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateEntityImplementations")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
annotatedElements.stream()
.map(TypeElement.class::cast)
.forEach(this::processTypeElement);
}
return true;
}
private void processTypeElement(TypeElement e) {
if (e.getKind() != ElementKind.INTERFACE) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to interface", e);
return;
}
// Find all properties
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = e.getEnclosedElements().stream()
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.filter(ee -> ! (ee.getReceiverType() instanceof NoType))
.collect(Collectors.toMap(this::determineAttributeFromMethodName, v -> new HashSet(Arrays.asList(v)), (a,b) -> { a.addAll(b); return a; }));
// Merge plurals with singulars
methodsPerAttribute.keySet().stream()
.filter(key -> methodsPerAttribute.containsKey(key + "s"))
.collect(Collectors.toSet())
.forEach(key -> {
HashSet<ExecutableElement> removed = methodsPerAttribute.remove(key);
methodsPerAttribute.get(key + "s").addAll(removed);
});
try {
generateImpl(e, methodsPerAttribute);
} catch (IOException ex) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate implementation for class", e);
}
// methodsPerAttribute.entrySet().stream()
// .sorted(Comparator.comparing(Map.Entry::getKey))
// .forEach(me -> processingEnv.getMessager().printMessage(
// Diagnostic.Kind.NOTE,
// "** " + me.getKey() + ": " + me.getValue().stream().map(ExecutableElement::getSimpleName).sorted(Comparator.comparing(Object::toString)).collect(Collectors.joining(", ")))
// );
}
private static final Pattern BEAN_NAME = Pattern.compile("(get|set|is|delete|remove|add|update)([A-Z]\\S+)");
private static final Map<String, String> FORBIDDEN_PREFIXES = new HashMap<>();
static {
FORBIDDEN_PREFIXES.put("delete", "remove");
}
private String determineAttributeFromMethodName(ExecutableElement e) {
Name name = e.getSimpleName();
Matcher m = BEAN_NAME.matcher(name.toString());
if (m.matches()) {
String prefix = m.group(1);
if (FORBIDDEN_PREFIXES.containsKey(prefix)) {
processingEnv.getMessager().printMessage(
Kind.ERROR,
"Forbidden prefix " + prefix + "... detected, use " + FORBIDDEN_PREFIXES.get(prefix) + "... instead", e
);
}
return m.group(2);
}
return null;
}
private void generateImpl(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
Elements elements = processingEnv.getElementUtils();
TypeElement parentTypeElement = elements.getTypeElement(an.inherits().isEmpty() ? "void" : an.inherits());
final List<? extends Element> allMembers = elements.getAllMembers(parentTypeElement);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String mapImplClassName = className + "Impl";
String mapSimpleClassName = simpleClassName + "Impl";
JavaFileObject enumFile = processingEnv.getFiler().createSourceFile(mapImplClassName);
try (PrintWriter pw = new PrintWriter(enumFile.openWriter()) {
@Override
public void println(String x) {
super.println(x == null ? x : x.replaceAll("java.lang.", ""));
}
}) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
pw.println("import java.util.EnumMap;");
pw.println("import java.util.Objects;");
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
pw.println(" public enum Field {");
methodsPerAttribute.keySet().stream()
.sorted()
.map(GenerateEntityImplementationsProcessor::toEnumConstant)
.forEach(key -> pw.println(" " + key + ","));
pw.println(" }");
pw.println(" private final EnumMap<Field, Object> values = new EnumMap<>(Field.class);");
pw.println(" protected Object get(Field field) { return values.get(field); }");
pw.println(" protected Object set(Field field, Object p0) { return values.put(field, p0); }");
// Constructors
allMembers.stream()
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
.forEach((ExecutableElement ee) -> pw.println(" public " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); }"));
for (Entry<String, HashSet<ExecutableElement>> me : methodsPerAttribute.entrySet()) {
String enumConstant = toEnumConstant(me.getKey());
HashSet<ExecutableElement> methods = me.getValue();
TypeMirror fieldType = determineFieldType(me.getKey(), methods);
if (fieldType == null) {
continue;
}
for (ExecutableElement method : methods) {
if (! printMethodBody(pw, method, me.getKey(), enumConstant, fieldType)) {
List<ExecutableElement> parentMethods = allMembers.stream()
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.filter(ee -> Objects.equals(ee.toString(), method.toString()))
.filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
.collect(Collectors.toList());
if (! parentMethods.isEmpty()) {
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.");
} else {
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
}
}
}
}
pw.println("}");
}
}
protected static String toEnumConstant(String key) {
return key.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase();
}
private TypeMirror determineFieldType(String fieldName, HashSet<ExecutableElement> methods) {
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
TypeMirror res = null;
for (ExecutableElement method : methods) {
if (getter.matcher(method.getSimpleName()).matches() && method.getParameters().isEmpty()) {
return method.getReturnType();
}
}
if (res == null) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine return type for field " + fieldName, methods.iterator().next());
}
return res;
}
private boolean printMethodBody(PrintWriter pw, ExecutableElement method, String fieldName, String enumConstant, TypeMirror fieldType) {
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
Types types = processingEnv.getTypeUtils();
final String methodName = method.getSimpleName().toString();
String setter = "set" + fieldName;
TypeMirror firstParameterType = method.getParameters().isEmpty()
? types.getNullType()
: method.getParameters().get(0).asType();
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
String getFromMap = "get" + fieldNameSingular;
String addToCollection = "add" + fieldNameSingular;
String updateMap = "set" + fieldNameSingular;
String removeFromCollection = "remove" + fieldNameSingular;
Elements elements = processingEnv.getElementUtils();
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
if (getter.matcher(methodName).matches() && method.getParameters().isEmpty() && types.isSameType(fieldType, method.getReturnType())) {
pw.println(" @Override public " + method.getReturnType() + " " + method + " {");
pw.println(" return (" + fieldType + ") get(Field." + enumConstant + ");");
pw.println(" }");
return true;
} else if (setter.equals(methodName) && types.isSameType(firstParameterType, fieldType)) {
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" Object o = set(Field." + enumConstant + ", p0);");
pw.println(" updated |= ! Objects.equals(o, p0);");
pw.println(" }");
return true;
} else if (addToCollection.equals(methodName) && method.getParameters().size() == 1) {
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
pw.println(" if (o == null) { o = " + interfaceToImplementation(typeElement) + "; set(Field." + enumConstant + ", o); }");
if (isSetType(typeElement)) {
pw.println(" updated |= o.add(p0);");
} else {
pw.println(" o.add(p0);");
pw.println(" updated = true;");
}
pw.println(" }");
return true;
} else if (removeFromCollection.equals(methodName) && method.getParameters().size() == 1) {
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
pw.println(" if (o == null) { return; }");
pw.println(" boolean removed = o.remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";");
pw.println(" updated |= removed;");
pw.println(" }");
return true;
} else if (updateMap.equals(methodName) && method.getParameters().size() == 2) {
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + method.getParameters().get(1).asType() + " p1) {");
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
pw.println(" if (o == null) { o = " + interfaceToImplementation(typeElement) + "; set(Field." + enumConstant + ", o); }");
pw.println(" Object v = o.put(p0, p1);");
pw.println(" updated |= ! Objects.equals(v, p1);");
pw.println(" }");
return true;
} else if (getFromMap.equals(methodName) && method.getParameters().size() == 1) {
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
pw.println(" return o == null ? null : o.get(p0);");
pw.println(" }");
return true;
}
return false;
}
private String interfaceToImplementation(TypeElement typeElement) {
GenerateEnumMapFieldType an = typeElement.getAnnotation(GenerateEnumMapFieldType.class);
if (an != null) {
return "new " + an.value().getCanonicalName() + "<>()";
}
Name parameterTypeQN = typeElement.getQualifiedName();
switch (parameterTypeQN.toString()) {
case "java.util.List":
return "new java.util.LinkedList<>()";
case "java.util.Map":
return "new java.util.HashMap<>()";
case "java.util.Set":
return "new java.util.HashSet<>()";
case "java.util.Collection":
return "new java.util.LinkedList<>()";
default:
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement);
return "TODO()";
}
}
private String methodParameters(List<? extends VariableElement> parameters) {
return parameters.stream()
.map(p -> p.asType() + " " + p.getSimpleName())
.collect(Collectors.joining(", "));
}
private static final HashSet<String> SET_TYPES = new HashSet<>(Arrays.asList(Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), HashSet.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName()));
private boolean isSetType(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
return SET_TYPES.contains(name.toString());
}
}

View file

@ -11,6 +11,27 @@
<name>Keycloak Model Map</name>
<description/>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-build-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>org.keycloak.models.map.processor.GenerateEntityImplementationsProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
@ -42,6 +63,13 @@
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-build-processor</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View file

@ -23,6 +23,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.security.MessageDigest;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -79,7 +80,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isEnabled() {
return entity.isEnabled();
final Boolean enabled = entity.isEnabled();
return enabled == null ? false : enabled;
}
@Override
@ -89,7 +91,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isAlwaysDisplayInConsole() {
return entity.isAlwaysDisplayInConsole();
final Boolean alwaysDisplayInConsole = entity.isAlwaysDisplayInConsole();
return alwaysDisplayInConsole == null ? false : alwaysDisplayInConsole;
}
@Override
@ -99,7 +102,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isSurrogateAuthRequired() {
return entity.isSurrogateAuthRequired();
final Boolean surrogateAuthRequired = entity.isSurrogateAuthRequired();
return surrogateAuthRequired == null ? false : surrogateAuthRequired;
}
@Override
@ -109,7 +113,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public Set<String> getWebOrigins() {
return entity.getWebOrigins();
final Set<String> webOrigins = entity.getWebOrigins();
return webOrigins == null ? Collections.emptySet() : webOrigins;
}
@Override
@ -129,7 +134,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public Set<String> getRedirectUris() {
return entity.getRedirectUris();
final Set<String> redirectUris = entity.getRedirectUris();
return redirectUris == null ? Collections.emptySet() : redirectUris;
}
@Override
@ -179,7 +185,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isBearerOnly() {
return entity.isBearerOnly();
final Boolean bearerOnly = entity.isBearerOnly();
return bearerOnly == null ? false : bearerOnly;
}
@Override
@ -214,7 +221,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public int getNodeReRegistrationTimeout() {
return entity.getNodeReRegistrationTimeout();
final Integer nodeReRegistrationTimeout = entity.getNodeReRegistrationTimeout();
return nodeReRegistrationTimeout == null ? 0 : nodeReRegistrationTimeout;
}
@Override
@ -265,13 +273,15 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public String getAttribute(String name) {
List<String> attribute = entity.getAttribute(name);
if (attribute.isEmpty()) return null;
if (attribute == null || attribute.isEmpty()) return null;
return attribute.get(0);
}
@Override
public Map<String, String> getAttributes() {
return entity.getAttributes().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
final Map<String, List<String>> attributes = entity.getAttributes();
final Map<String, List<String>> a = attributes == null ? Collections.emptyMap() : attributes;
return a.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
entry -> {
if (entry.getValue().isEmpty()) return null;
return entry.getValue().get(0);
@ -286,7 +296,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return entity.getAuthenticationFlowBindingOverrides();
final Map<String, String> authenticationFlowBindingOverrides = entity.getAuthenticationFlowBindingOverrides();
return authenticationFlowBindingOverrides == null ? Collections.emptyMap() : authenticationFlowBindingOverrides;
}
@Override
@ -301,7 +312,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isFrontchannelLogout() {
return entity.isFrontchannelLogout();
final Boolean frontchannelLogout = entity.isFrontchannelLogout();
return frontchannelLogout == null ? false : frontchannelLogout;
}
@Override
@ -311,7 +323,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isFullScopeAllowed() {
return entity.isFullScopeAllowed();
final Boolean fullScopeAllowed = entity.isFullScopeAllowed();
return fullScopeAllowed == null ? false : fullScopeAllowed;
}
@Override
@ -321,7 +334,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isPublicClient() {
return entity.isPublicClient();
final Boolean publicClient = entity.isPublicClient();
return publicClient == null ? false : publicClient;
}
@Override
@ -331,7 +345,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isConsentRequired() {
return entity.isConsentRequired();
final Boolean consentRequired = entity.isConsentRequired();
return consentRequired == null ? false : consentRequired;
}
@Override
@ -341,7 +356,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isStandardFlowEnabled() {
return entity.isStandardFlowEnabled();
final Boolean standardFlowEnabled = entity.isStandardFlowEnabled();
return standardFlowEnabled == null ? false : standardFlowEnabled;
}
@Override
@ -351,7 +367,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isImplicitFlowEnabled() {
return entity.isImplicitFlowEnabled();
final Boolean implicitFlowEnabled = entity.isImplicitFlowEnabled();
return implicitFlowEnabled == null ? false : implicitFlowEnabled;
}
@Override
@ -361,7 +378,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isDirectAccessGrantsEnabled() {
return entity.isDirectAccessGrantsEnabled();
final Boolean directAccessGrantsEnabled = entity.isDirectAccessGrantsEnabled();
return directAccessGrantsEnabled == null ? false : directAccessGrantsEnabled;
}
@Override
@ -371,7 +389,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public boolean isServiceAccountsEnabled() {
return entity.isServiceAccountsEnabled();
final Boolean serviceAccountsEnabled = entity.isServiceAccountsEnabled();
return serviceAccountsEnabled == null ? false : serviceAccountsEnabled;
}
@Override
@ -386,7 +405,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public int getNotBefore() {
return entity.getNotBefore();
final Integer notBefore = entity.getNotBefore();
return notBefore == null ? 0 : notBefore;
}
@Override
@ -398,7 +418,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public Stream<RoleModel> getScopeMappingsStream() {
return this.entity.getScopeMappings().stream()
final Collection<String> scopeMappings = this.entity.getScopeMappings();
return scopeMappings == null ? Stream.empty() : scopeMappings.stream()
.map(realm::getRoleById)
.filter(Objects::nonNull);
}
@ -415,7 +436,7 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
public void deleteScopeMapping(RoleModel role) {
final String id = role == null ? null : role.getId();
if (id != null) {
this.entity.deleteScopeMapping(id);
this.entity.removeScopeMapping(id);
}
}
@ -434,7 +455,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
if (isFullScopeAllowed()) return true;
final String id = role == null ? null : role.getId();
if (id != null && this.entity.getScopeMappings().contains(id)) {
final Collection<String> scopeMappings = this.entity.getScopeMappings();
if (id != null && scopeMappings != null && scopeMappings.contains(id)) {
return true;
}
@ -483,7 +505,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
@Override
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return entity.getProtocolMappers().stream().distinct();
final Map<String, ProtocolMapperModel> protocolMappers = entity.getProtocolMappers();
return protocolMappers == null ? Stream.empty() : protocolMappers.values().stream().distinct();
}
@Override
@ -504,7 +527,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
pm.setConfig(new HashMap<>());
}
return entity.addProtocolMapper(pm);
entity.setProtocolMapper(pm.getId(), pm);
return pm;
}
@Override
@ -519,18 +543,19 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
public void updateProtocolMapper(ProtocolMapperModel mapping) {
final String id = mapping == null ? null : mapping.getId();
if (id != null) {
entity.updateProtocolMapper(id, mapping);
entity.setProtocolMapper(id, mapping);
}
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return entity.getProtocolMapperById(id);
return entity.getProtocolMapper(id);
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
return entity.getProtocolMappers().stream()
final Map<String, ProtocolMapperModel> protocolMappers = entity.getProtocolMappers();
return protocolMappers == null ? null : protocolMappers.values().stream()
.filter(pm -> Objects.equals(pm.getProtocol(), protocol) && Objects.equals(pm.getName(), name))
.findAny()
.orElse(null);

View file

@ -22,36 +22,89 @@ import org.keycloak.models.map.common.UpdatableEntity;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
/**
*
* @author hmlnarik
*/
@GenerateEntityImplementations(inherits="org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity")
public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
void addClientScope(String id, Boolean defaultScope);
static abstract class AbstractClientEntity implements MapClientEntity {
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private String id;
ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model);
protected AbstractClientEntity() {
this.id = null;
}
public AbstractClientEntity(String id) {
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
@Override
public Stream<String> getClientScopes(boolean defaultScope) {
final Map<String, Boolean> clientScopes = getClientScopes();
return clientScopes == null ? Stream.empty() : clientScopes.entrySet().stream()
.filter(me -> Objects.equals(me.getValue(), defaultScope))
.map(Entry::getKey);
}
}
Map<String, Boolean> getClientScopes();
Stream<String> getClientScopes(boolean defaultScope);
void setClientScope(String id, Boolean defaultScope);
void removeClientScope(String id);
ProtocolMapperModel getProtocolMapper(String id);
Map<String, ProtocolMapperModel> getProtocolMappers();
void setProtocolMapper(String id, ProtocolMapperModel mapping);
void removeProtocolMapper(String id);
void addRedirectUri(String redirectUri);
Set<String> getRedirectUris();
void removeRedirectUri(String redirectUri);
void setRedirectUris(Set<String> redirectUris);
void addScopeMapping(String id);
void removeScopeMapping(String id);
Collection<String> getScopeMappings();
void addWebOrigin(String webOrigin);
Set<String> getWebOrigins();
void removeWebOrigin(String webOrigin);
void setWebOrigins(Set<String> webOrigins);
void deleteScopeMapping(String id);
List<String> getAttribute(String name);
default List<String> getAttribute(String name) { return getAttributes().get(name); }
Map<String, List<String>> getAttributes();
void removeAttribute(String name);
void setAttribute(String name, List<String> values);
Map<String, String> getAuthFlowBindings();
void setAuthFlowBindings(Map<String, String> authFlowBindings);
String getAuthenticationFlowBindingOverride(String binding);
Map<String, String> getAuthenticationFlowBindingOverrides();
void removeAuthenticationFlowBindingOverride(String binding);
void setAuthenticationFlowBindingOverride(String binding, String flowId);
String getBaseUrl();
@ -59,40 +112,28 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
String getClientId();
Stream<String> getClientScopes(boolean defaultScope);
String getDescription();
String getManagementUrl();
String getName();
int getNodeReRegistrationTimeout();
Integer getNodeReRegistrationTimeout();
int getNotBefore();
Integer getNotBefore();
String getProtocol();
ProtocolMapperModel getProtocolMapperById(String id);
Collection<ProtocolMapperModel> getProtocolMappers();
String getRealmId();
Set<String> getRedirectUris();
String getRegistrationToken();
String getRootUrl();
Set<String> getScope();
Collection<String> getScopeMappings();
String getSecret();
Set<String> getWebOrigins();
Boolean isAlwaysDisplayInConsole();
Boolean isBearerOnly();
@ -117,26 +158,8 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
Boolean isSurrogateAuthRequired();
void removeAttribute(String name);
void removeAuthenticationFlowBindingOverride(String binding);
void removeClientScope(String id);
void removeProtocolMapper(String id);
void removeRedirectUri(String redirectUri);
void removeWebOrigin(String webOrigin);
void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole);
void setAttribute(String name, List<String> values);
void setAuthFlowBindings(Map<String, String> authFlowBindings);
void setAuthenticationFlowBindingOverride(String binding, String flowId);
void setBaseUrl(String baseUrl);
void setBearerOnly(Boolean bearerOnly);
@ -163,17 +186,15 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
void setName(String name);
void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout);
void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout);
void setNotBefore(int notBefore);
void setNotBefore(Integer notBefore);
void setProtocol(String protocol);
void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers);
void setPublicClient(Boolean publicClient);
void setRedirectUris(Set<String> redirectUris);
void setRealmId(String realmId);
void setRegistrationToken(String registrationToken);
@ -189,8 +210,4 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
void setSurrogateAuthRequired(Boolean surrogateAuthRequired);
void setWebOrigins(Set<String> webOrigins);
void updateProtocolMapper(String id, ProtocolMapperModel mapping);
}

View file

@ -30,7 +30,7 @@ public class MapClientEntityDelegate extends MapClientEntityLazyDelegate {
}
@Override
protected MapClientEntity getDelegate() {
protected MapClientEntity getWriteDelegate() {
return delegate;
}
}

View file

@ -1,557 +0,0 @@
/*
* Copyright 2020 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.models.map.client;
import org.keycloak.models.ProtocolMapperModel;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author hmlnarik
*/
public class MapClientEntityImpl implements MapClientEntity {
private String id;
private String realmId;
private String clientId;
private String name;
private String description;
private Set<String> redirectUris = new HashSet<>();
private boolean enabled;
private boolean alwaysDisplayInConsole;
private String clientAuthenticatorType;
private String secret;
private String registrationToken;
private String protocol;
private Map<String, List<String>> attributes = new HashMap<>();
private Map<String, String> authFlowBindings = new HashMap<>();
private boolean publicClient;
private boolean fullScopeAllowed;
private boolean frontchannelLogout;
private int notBefore;
private Set<String> scope = new HashSet<>();
private Set<String> webOrigins = new HashSet<>();
private Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
private Map<String, Boolean> clientScopes = new HashMap<>();
private Set<String> scopeMappings = new LinkedHashSet<>();
private boolean surrogateAuthRequired;
private String managementUrl;
private String rootUrl;
private String baseUrl;
private boolean bearerOnly;
private boolean consentRequired;
private boolean standardFlowEnabled;
private boolean implicitFlowEnabled;
private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private int nodeReRegistrationTimeout;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected MapClientEntityImpl() {
this.id = null;
this.realmId = null;
}
public MapClientEntityImpl(String id, String realmId) {
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public String getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
@Override
public String getClientId() {
return clientId;
}
@Override
public void setClientId(String clientId) {
this.updated |= ! Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
@Override
public Set<String> getRedirectUris() {
return redirectUris;
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
this.updated |= ! Objects.equals(this.redirectUris, redirectUris);
this.redirectUris = redirectUris;
}
@Override
public Boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(Boolean enabled) {
this.updated |= ! Objects.equals(this.enabled, enabled);
this.enabled = enabled;
}
@Override
public Boolean isAlwaysDisplayInConsole() {
return alwaysDisplayInConsole;
}
@Override
public void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole) {
this.updated |= ! Objects.equals(this.alwaysDisplayInConsole, alwaysDisplayInConsole);
this.alwaysDisplayInConsole = alwaysDisplayInConsole;
}
@Override
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.updated |= ! Objects.equals(this.clientAuthenticatorType, clientAuthenticatorType);
this.clientAuthenticatorType = clientAuthenticatorType;
}
@Override
public String getSecret() {
return secret;
}
@Override
public void setSecret(String secret) {
this.updated |= ! Objects.equals(this.secret, secret);
this.secret = secret;
}
@Override
public String getRegistrationToken() {
return registrationToken;
}
@Override
public void setRegistrationToken(String registrationToken) {
this.updated |= ! Objects.equals(this.registrationToken, registrationToken);
this.registrationToken = registrationToken;
}
@Override
public String getProtocol() {
return protocol;
}
@Override
public void setProtocol(String protocol) {
this.updated |= ! Objects.equals(this.protocol, protocol);
this.protocol = protocol;
}
@Override
public Map<String, List<String>> getAttributes() {
return attributes;
}
@Override
public void setAttribute(String name, List<String> values) {
this.updated |= ! Objects.equals(this.attributes.put(name, values), values);
}
@Override
public Map<String, String> getAuthFlowBindings() {
return authFlowBindings;
}
@Override
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
this.updated |= ! Objects.equals(this.authFlowBindings, authFlowBindings);
this.authFlowBindings = authFlowBindings;
}
@Override
public Boolean isPublicClient() {
return publicClient;
}
@Override
public void setPublicClient(Boolean publicClient) {
this.updated |= ! Objects.equals(this.publicClient, publicClient);
this.publicClient = publicClient;
}
@Override
public Boolean isFullScopeAllowed() {
return fullScopeAllowed;
}
@Override
public void setFullScopeAllowed(Boolean fullScopeAllowed) {
this.updated |= ! Objects.equals(this.fullScopeAllowed, fullScopeAllowed);
this.fullScopeAllowed = fullScopeAllowed;
}
@Override
public Boolean isFrontchannelLogout() {
return frontchannelLogout;
}
@Override
public void setFrontchannelLogout(Boolean frontchannelLogout) {
this.updated |= ! Objects.equals(this.frontchannelLogout, frontchannelLogout);
this.frontchannelLogout = frontchannelLogout;
}
@Override
public int getNotBefore() {
return notBefore;
}
@Override
public void setNotBefore(int notBefore) {
this.updated |= ! Objects.equals(this.notBefore, notBefore);
this.notBefore = notBefore;
}
@Override
public Set<String> getScope() {
return scope;
}
@Override
public void setScope(Set<String> scope) {
this.updated |= ! Objects.equals(this.scope, scope);
this.scope.clear();
this.scope.addAll(scope);
}
@Override
public Set<String> getWebOrigins() {
return webOrigins;
}
@Override
public void setWebOrigins(Set<String> webOrigins) {
this.updated |= ! Objects.equals(this.webOrigins, webOrigins);
this.webOrigins.clear();
this.webOrigins.addAll(webOrigins);
}
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
Objects.requireNonNull(model.getId(), "protocolMapper.id");
updated = true;
this.protocolMappers.put(model.getId(), model);
return model;
}
@Override
public Collection<ProtocolMapperModel> getProtocolMappers() {
return protocolMappers.values();
}
@Override
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
updated = true;
protocolMappers.put(id, mapping);
}
@Override
public void removeProtocolMapper(String id) {
updated |= protocolMappers.remove(id) != null;
}
@Override
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
this.protocolMappers.clear();
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return id == null ? null : protocolMappers.get(id);
}
@Override
public Boolean isSurrogateAuthRequired() {
return surrogateAuthRequired;
}
@Override
public void setSurrogateAuthRequired(Boolean surrogateAuthRequired) {
this.updated |= ! Objects.equals(this.surrogateAuthRequired, surrogateAuthRequired);
this.surrogateAuthRequired = surrogateAuthRequired;
}
@Override
public String getManagementUrl() {
return managementUrl;
}
@Override
public void setManagementUrl(String managementUrl) {
this.updated |= ! Objects.equals(this.managementUrl, managementUrl);
this.managementUrl = managementUrl;
}
@Override
public String getRootUrl() {
return rootUrl;
}
@Override
public void setRootUrl(String rootUrl) {
this.updated |= ! Objects.equals(this.rootUrl, rootUrl);
this.rootUrl = rootUrl;
}
@Override
public String getBaseUrl() {
return baseUrl;
}
@Override
public void setBaseUrl(String baseUrl) {
this.updated |= ! Objects.equals(this.baseUrl, baseUrl);
this.baseUrl = baseUrl;
}
@Override
public Boolean isBearerOnly() {
return bearerOnly;
}
@Override
public void setBearerOnly(Boolean bearerOnly) {
this.updated |= ! Objects.equals(this.bearerOnly, bearerOnly);
this.bearerOnly = bearerOnly;
}
@Override
public Boolean isConsentRequired() {
return consentRequired;
}
@Override
public void setConsentRequired(Boolean consentRequired) {
this.updated |= ! Objects.equals(this.consentRequired, consentRequired);
this.consentRequired = consentRequired;
}
@Override
public Boolean isStandardFlowEnabled() {
return standardFlowEnabled;
}
@Override
public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
this.updated |= ! Objects.equals(this.standardFlowEnabled, standardFlowEnabled);
this.standardFlowEnabled = standardFlowEnabled;
}
@Override
public Boolean isImplicitFlowEnabled() {
return implicitFlowEnabled;
}
@Override
public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
this.updated |= ! Objects.equals(this.implicitFlowEnabled, implicitFlowEnabled);
this.implicitFlowEnabled = implicitFlowEnabled;
}
@Override
public Boolean isDirectAccessGrantsEnabled() {
return directAccessGrantsEnabled;
}
@Override
public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
this.updated |= ! Objects.equals(this.directAccessGrantsEnabled, directAccessGrantsEnabled);
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
}
@Override
public Boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
@Override
public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) {
this.updated |= ! Objects.equals(this.serviceAccountsEnabled, serviceAccountsEnabled);
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
@Override
public int getNodeReRegistrationTimeout() {
return nodeReRegistrationTimeout;
}
@Override
public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
this.updated |= ! Objects.equals(this.nodeReRegistrationTimeout, nodeReRegistrationTimeout);
this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
}
@Override
public void addWebOrigin(String webOrigin) {
updated = true;
this.webOrigins.add(webOrigin);
}
@Override
public void removeWebOrigin(String webOrigin) {
updated |= this.webOrigins.remove(webOrigin);
}
@Override
public void addRedirectUri(String redirectUri) {
this.updated |= ! this.redirectUris.contains(redirectUri);
this.redirectUris.add(redirectUri);
}
@Override
public void removeRedirectUri(String redirectUri) {
updated |= this.redirectUris.remove(redirectUri);
}
@Override
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
@Override
public List<String> getAttribute(String name) {
return attributes.getOrDefault(name, Collections.EMPTY_LIST);
}
@Override
public String getAuthenticationFlowBindingOverride(String binding) {
return this.authFlowBindings.get(binding);
}
@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return this.authFlowBindings;
}
@Override
public void removeAuthenticationFlowBindingOverride(String binding) {
updated |= this.authFlowBindings.remove(binding) != null;
}
@Override
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
this.updated = true;
this.authFlowBindings.put(binding, flowId);
}
@Override
public Collection<String> getScopeMappings() {
return scopeMappings;
}
@Override
public void addScopeMapping(String id) {
if (id != null) {
updated = true;
scopeMappings.add(id);
}
}
@Override
public void deleteScopeMapping(String id) {
updated |= scopeMappings.remove(id);
}
@Override
public void addClientScope(String id, Boolean defaultScope) {
if (id != null) {
updated = true;
this.clientScopes.put(id, defaultScope);
}
}
@Override
public void removeClientScope(String id) {
if (id != null) {
updated |= clientScopes.remove(id) != null;
}
}
@Override
public Stream<String> getClientScopes(boolean defaultScope) {
return this.clientScopes.entrySet().stream()
.filter(me -> Objects.equals(me.getValue(), defaultScope))
.map(Entry::getKey);
}
@Override
public String getRealmId() {
return this.realmId;
}
}

View file

@ -39,8 +39,8 @@ public class MapClientEntityLazyDelegate implements MapClientEntity {
this.delegateSupplier = delegateSupplier;
}
protected MapClientEntity getDelegate() {
if (! delegate.isMarked()) {
protected MapClientEntity getWriteDelegate() {
if (! isWriteDelegateInitialized()) {
delegate.compareAndSet(null, delegateSupplier == null ? null : delegateSupplier.get(), false, true);
}
MapClientEntity ref = delegate.getReference();
@ -50,419 +50,427 @@ public class MapClientEntityLazyDelegate implements MapClientEntity {
return ref;
}
@Override
public void addClientScope(String id, Boolean defaultScope) {
getDelegate().addClientScope(id, defaultScope);
protected boolean isWriteDelegateInitialized() {
return delegate.isMarked();
}
protected MapClientEntity getReadDelegate() {
return getWriteDelegate();
}
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
return getDelegate().addProtocolMapper(model);
public void setClientScope(String id, Boolean defaultScope) {
getWriteDelegate().setClientScope(id, defaultScope);
}
@Override
public void addRedirectUri(String redirectUri) {
getDelegate().addRedirectUri(redirectUri);
getWriteDelegate().addRedirectUri(redirectUri);
}
@Override
public void addScopeMapping(String id) {
getDelegate().addScopeMapping(id);
getWriteDelegate().addScopeMapping(id);
}
@Override
public void addWebOrigin(String webOrigin) {
getDelegate().addWebOrigin(webOrigin);
getWriteDelegate().addWebOrigin(webOrigin);
}
@Override
public void deleteScopeMapping(String id) {
getDelegate().deleteScopeMapping(id);
public void removeScopeMapping(String id) {
getWriteDelegate().removeScopeMapping(id);
}
@Override
public List<String> getAttribute(String name) {
return getDelegate().getAttribute(name);
return getReadDelegate().getAttribute(name);
}
@Override
public Map<String, List<String>> getAttributes() {
return getDelegate().getAttributes();
return getReadDelegate().getAttributes();
}
@Override
public Map<String, String> getAuthFlowBindings() {
return getDelegate().getAuthFlowBindings();
return getReadDelegate().getAuthFlowBindings();
}
@Override
public String getAuthenticationFlowBindingOverride(String binding) {
return getDelegate().getAuthenticationFlowBindingOverride(binding);
return getReadDelegate().getAuthenticationFlowBindingOverride(binding);
}
@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return getDelegate().getAuthenticationFlowBindingOverrides();
return getReadDelegate().getAuthenticationFlowBindingOverrides();
}
@Override
public String getBaseUrl() {
return getDelegate().getBaseUrl();
return getReadDelegate().getBaseUrl();
}
@Override
public String getClientAuthenticatorType() {
return getDelegate().getClientAuthenticatorType();
return getReadDelegate().getClientAuthenticatorType();
}
@Override
public String getClientId() {
return getDelegate().getClientId();
return getReadDelegate().getClientId();
}
@Override
public Stream<String> getClientScopes(boolean defaultScope) {
return getDelegate().getClientScopes(defaultScope);
return getReadDelegate().getClientScopes(defaultScope);
}
@Override
public Map<String, Boolean> getClientScopes() {
return getReadDelegate().getClientScopes();
}
@Override
public String getDescription() {
return getDelegate().getDescription();
return getReadDelegate().getDescription();
}
@Override
public String getManagementUrl() {
return getDelegate().getManagementUrl();
return getReadDelegate().getManagementUrl();
}
@Override
public String getName() {
return getDelegate().getName();
return getReadDelegate().getName();
}
@Override
public int getNodeReRegistrationTimeout() {
return getDelegate().getNodeReRegistrationTimeout();
public Integer getNodeReRegistrationTimeout() {
return getReadDelegate().getNodeReRegistrationTimeout();
}
@Override
public int getNotBefore() {
return getDelegate().getNotBefore();
public Integer getNotBefore() {
return getReadDelegate().getNotBefore();
}
@Override
public String getProtocol() {
return getDelegate().getProtocol();
return getReadDelegate().getProtocol();
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return getDelegate().getProtocolMapperById(id);
public ProtocolMapperModel getProtocolMapper(String id) {
return getReadDelegate().getProtocolMapper(id);
}
@Override
public Collection<ProtocolMapperModel> getProtocolMappers() {
return getDelegate().getProtocolMappers();
public Map<String,ProtocolMapperModel> getProtocolMappers() {
return getReadDelegate().getProtocolMappers();
}
@Override
public String getRealmId() {
return getDelegate().getRealmId();
return getReadDelegate().getRealmId();
}
@Override
public void setRealmId(String realmId) {
getWriteDelegate().setRealmId(realmId);
}
@Override
public Set<String> getRedirectUris() {
return getDelegate().getRedirectUris();
return getReadDelegate().getRedirectUris();
}
@Override
public String getRegistrationToken() {
return getDelegate().getRegistrationToken();
return getReadDelegate().getRegistrationToken();
}
@Override
public String getRootUrl() {
return getDelegate().getRootUrl();
return getReadDelegate().getRootUrl();
}
@Override
public Set<String> getScope() {
return getDelegate().getScope();
return getReadDelegate().getScope();
}
@Override
public Collection<String> getScopeMappings() {
return getDelegate().getScopeMappings();
return getReadDelegate().getScopeMappings();
}
@Override
public String getSecret() {
return getDelegate().getSecret();
return getReadDelegate().getSecret();
}
@Override
public Set<String> getWebOrigins() {
return getDelegate().getWebOrigins();
return getReadDelegate().getWebOrigins();
}
@Override
public Boolean isAlwaysDisplayInConsole() {
return getDelegate().isAlwaysDisplayInConsole();
return getWriteDelegate().isAlwaysDisplayInConsole();
}
@Override
public Boolean isBearerOnly() {
return getDelegate().isBearerOnly();
return getWriteDelegate().isBearerOnly();
}
@Override
public Boolean isConsentRequired() {
return getDelegate().isConsentRequired();
return getWriteDelegate().isConsentRequired();
}
@Override
public Boolean isDirectAccessGrantsEnabled() {
return getDelegate().isDirectAccessGrantsEnabled();
return getWriteDelegate().isDirectAccessGrantsEnabled();
}
@Override
public Boolean isEnabled() {
return getDelegate().isEnabled();
return getWriteDelegate().isEnabled();
}
@Override
public Boolean isFrontchannelLogout() {
return getDelegate().isFrontchannelLogout();
return getWriteDelegate().isFrontchannelLogout();
}
@Override
public Boolean isFullScopeAllowed() {
return getDelegate().isFullScopeAllowed();
return getWriteDelegate().isFullScopeAllowed();
}
@Override
public Boolean isImplicitFlowEnabled() {
return getDelegate().isImplicitFlowEnabled();
return getWriteDelegate().isImplicitFlowEnabled();
}
@Override
public Boolean isPublicClient() {
return getDelegate().isPublicClient();
return getWriteDelegate().isPublicClient();
}
@Override
public Boolean isServiceAccountsEnabled() {
return getDelegate().isServiceAccountsEnabled();
return getWriteDelegate().isServiceAccountsEnabled();
}
@Override
public Boolean isStandardFlowEnabled() {
return getDelegate().isStandardFlowEnabled();
return getWriteDelegate().isStandardFlowEnabled();
}
@Override
public Boolean isSurrogateAuthRequired() {
return getDelegate().isSurrogateAuthRequired();
return getWriteDelegate().isSurrogateAuthRequired();
}
@Override
public void removeAttribute(String name) {
getDelegate().removeAttribute(name);
getWriteDelegate().removeAttribute(name);
}
@Override
public void removeAuthenticationFlowBindingOverride(String binding) {
getDelegate().removeAuthenticationFlowBindingOverride(binding);
getWriteDelegate().removeAuthenticationFlowBindingOverride(binding);
}
@Override
public void removeClientScope(String id) {
getDelegate().removeClientScope(id);
getWriteDelegate().removeClientScope(id);
}
@Override
public void removeProtocolMapper(String id) {
getDelegate().removeProtocolMapper(id);
getWriteDelegate().removeProtocolMapper(id);
}
@Override
public void removeRedirectUri(String redirectUri) {
getDelegate().removeRedirectUri(redirectUri);
getWriteDelegate().removeRedirectUri(redirectUri);
}
@Override
public void removeWebOrigin(String webOrigin) {
getDelegate().removeWebOrigin(webOrigin);
getWriteDelegate().removeWebOrigin(webOrigin);
}
@Override
public void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole) {
getDelegate().setAlwaysDisplayInConsole(alwaysDisplayInConsole);
getWriteDelegate().setAlwaysDisplayInConsole(alwaysDisplayInConsole);
}
@Override
public void setAttribute(String name, List<String> values) {
getDelegate().setAttribute(name, values);
getWriteDelegate().setAttribute(name, values);
}
@Override
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
getDelegate().setAuthFlowBindings(authFlowBindings);
getWriteDelegate().setAuthFlowBindings(authFlowBindings);
}
@Override
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
getDelegate().setAuthenticationFlowBindingOverride(binding, flowId);
getWriteDelegate().setAuthenticationFlowBindingOverride(binding, flowId);
}
@Override
public void setBaseUrl(String baseUrl) {
getDelegate().setBaseUrl(baseUrl);
getWriteDelegate().setBaseUrl(baseUrl);
}
@Override
public void setBearerOnly(Boolean bearerOnly) {
getDelegate().setBearerOnly(bearerOnly);
getWriteDelegate().setBearerOnly(bearerOnly);
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
getDelegate().setClientAuthenticatorType(clientAuthenticatorType);
getWriteDelegate().setClientAuthenticatorType(clientAuthenticatorType);
}
@Override
public void setClientId(String clientId) {
getDelegate().setClientId(clientId);
getWriteDelegate().setClientId(clientId);
}
@Override
public void setConsentRequired(Boolean consentRequired) {
getDelegate().setConsentRequired(consentRequired);
getWriteDelegate().setConsentRequired(consentRequired);
}
@Override
public void setDescription(String description) {
getDelegate().setDescription(description);
getWriteDelegate().setDescription(description);
}
@Override
public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
getDelegate().setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
getWriteDelegate().setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
}
@Override
public void setEnabled(Boolean enabled) {
getDelegate().setEnabled(enabled);
getWriteDelegate().setEnabled(enabled);
}
@Override
public void setFrontchannelLogout(Boolean frontchannelLogout) {
getDelegate().setFrontchannelLogout(frontchannelLogout);
getWriteDelegate().setFrontchannelLogout(frontchannelLogout);
}
@Override
public void setFullScopeAllowed(Boolean fullScopeAllowed) {
getDelegate().setFullScopeAllowed(fullScopeAllowed);
getWriteDelegate().setFullScopeAllowed(fullScopeAllowed);
}
@Override
public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
getDelegate().setImplicitFlowEnabled(implicitFlowEnabled);
getWriteDelegate().setImplicitFlowEnabled(implicitFlowEnabled);
}
@Override
public void setManagementUrl(String managementUrl) {
getDelegate().setManagementUrl(managementUrl);
getWriteDelegate().setManagementUrl(managementUrl);
}
@Override
public void setName(String name) {
getDelegate().setName(name);
getWriteDelegate().setName(name);
}
@Override
public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
getDelegate().setNodeReRegistrationTimeout(nodeReRegistrationTimeout);
public void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout) {
getWriteDelegate().setNodeReRegistrationTimeout(nodeReRegistrationTimeout);
}
@Override
public void setNotBefore(int notBefore) {
getDelegate().setNotBefore(notBefore);
public void setNotBefore(Integer notBefore) {
getWriteDelegate().setNotBefore(notBefore);
}
@Override
public void setProtocol(String protocol) {
getDelegate().setProtocol(protocol);
}
@Override
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
getDelegate().setProtocolMappers(protocolMappers);
getWriteDelegate().setProtocol(protocol);
}
@Override
public void setPublicClient(Boolean publicClient) {
getDelegate().setPublicClient(publicClient);
getWriteDelegate().setPublicClient(publicClient);
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
getDelegate().setRedirectUris(redirectUris);
getWriteDelegate().setRedirectUris(redirectUris);
}
@Override
public void setRegistrationToken(String registrationToken) {
getDelegate().setRegistrationToken(registrationToken);
getWriteDelegate().setRegistrationToken(registrationToken);
}
@Override
public void setRootUrl(String rootUrl) {
getDelegate().setRootUrl(rootUrl);
getWriteDelegate().setRootUrl(rootUrl);
}
@Override
public void setScope(Set<String> scope) {
getDelegate().setScope(scope);
getWriteDelegate().setScope(scope);
}
@Override
public void setSecret(String secret) {
getDelegate().setSecret(secret);
getWriteDelegate().setSecret(secret);
}
@Override
public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) {
getDelegate().setServiceAccountsEnabled(serviceAccountsEnabled);
getWriteDelegate().setServiceAccountsEnabled(serviceAccountsEnabled);
}
@Override
public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
getDelegate().setStandardFlowEnabled(standardFlowEnabled);
getWriteDelegate().setStandardFlowEnabled(standardFlowEnabled);
}
@Override
public void setSurrogateAuthRequired(Boolean surrogateAuthRequired) {
getDelegate().setSurrogateAuthRequired(surrogateAuthRequired);
getWriteDelegate().setSurrogateAuthRequired(surrogateAuthRequired);
}
@Override
public void setWebOrigins(Set<String> webOrigins) {
getDelegate().setWebOrigins(webOrigins);
getWriteDelegate().setWebOrigins(webOrigins);
}
@Override
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
getDelegate().updateProtocolMapper(id, mapping);
public void setProtocolMapper(String id, ProtocolMapperModel mapping) {
getWriteDelegate().setProtocolMapper(id, mapping);
}
@Override
public String getId() {
return getDelegate().getId();
return getReadDelegate().getId();
}
@Override
public boolean isUpdated() {
return getDelegate().isUpdated();
return isWriteDelegateInitialized() && getWriteDelegate().isUpdated();
}
}

View file

@ -140,7 +140,8 @@ public class MapClientProvider implements ClientProvider {
public ClientModel addClient(RealmModel realm, String id, String clientId) {
LOG.tracef("addClient(%s, %s, %s)%s", realm, id, clientId, getShortStackTrace());
MapClientEntity entity = new MapClientEntityImpl(id, realm.getId());
MapClientEntity entity = new MapClientEntityImpl(id);
entity.setRealmId(realm.getId());
entity.setClientId(clientId);
entity.setEnabled(true);
entity.setStandardFlowEnabled(true);
@ -294,7 +295,7 @@ public class MapClientProvider implements ClientProvider {
clientScopes.stream()
.filter(clientScope -> ! existingClientScopes.containsKey(clientScope.getName()))
.filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol))
.forEach(clientScope -> entity.addClientScope(clientScope.getId(), defaultScope));
.forEach(clientScope -> entity.setClientScope(clientScope.getId(), defaultScope));
}
@Override

View file

@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jdk8.StreamSerializer;
@ -51,6 +52,7 @@ public class Serialization {
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
.setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
.activateDefaultTyping(new LaissezFaireSubTypeValidator() /* TODO - see javadoc */, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY)
.addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class)
.addMixIn(AbstractEntity.class, AbstractEntityMixIn.class)
;

View file

@ -62,14 +62,15 @@ public class ConcurrentHashMapKeycloakTransaction<K, V extends AbstractEntity &
@Override
public void commit() {
log.tracef("Commit - %s", map);
if (rollback) {
throw new RuntimeException("Rollback only!");
}
for (MapTaskWithValue value : tasks.values()) {
value.execute();
if (! tasks.isEmpty()) {
log.tracef("Commit - %s", map);
for (MapTaskWithValue value : tasks.values()) {
value.execute();
}
}
}

View file

@ -66,7 +66,9 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Optional;
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
/**
@ -261,14 +263,14 @@ public class MapFieldPredicates {
private static MapModelCriteriaBuilder<Object, MapClientEntity, ClientModel> checkScopeMappingRole(MapModelCriteriaBuilder<Object, MapClientEntity, ClientModel> mcb, Operator op, Object[] values) {
String roleIdS = ensureEqSingleValue(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "role_id", op, values);
Function<MapClientEntity, ?> getter;
getter = ce -> ce.getScopeMappings().contains(roleIdS);
getter = ce -> Optional.ofNullable(ce.getScopeMappings()).orElse(Collections.emptyList()).contains(roleIdS);
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, MapGroupEntity, GroupModel> checkGrantedGroupRole(MapModelCriteriaBuilder<Object, MapGroupEntity, GroupModel> mcb, Operator op, Object[] values) {
String roleIdS = ensureEqSingleValue(GroupModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values);
Function<MapGroupEntity, ?> getter;
getter = ge -> ge.getGrantedRoles().contains(roleIdS);
getter = ge -> Optional.ofNullable(ge.getGrantedRoles()).orElse(Collections.emptySet()).contains(roleIdS);
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}

View file

@ -34,5 +34,6 @@
<module>jpa</module>
<module>infinispan</module>
<module>map</module>
<module>build-processor</module>
</modules>
</project>

View file

@ -19,6 +19,7 @@ package org.keycloak.models;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
/**
* Specifies a mapping from user data to a protocol claim assertion.
@ -78,17 +79,42 @@ public class ProtocolMapperModel implements Serializable {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProtocolMapperModel that = (ProtocolMapperModel) o;
if (!id.equals(that.id)) return false;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ProtocolMapperModel other = (ProtocolMapperModel) obj;
if (this.consentRequired != other.consentRequired) {
return false;
}
if ( ! Objects.equals(this.id, other.id)) {
return false;
}
if ( ! Objects.equals(this.name, other.name)) {
return false;
}
if ( ! Objects.equals(this.protocol, other.protocol)) {
return false;
}
if ( ! Objects.equals(this.protocolMapper, other.protocolMapper)) {
return false;
}
if ( ! Objects.equals(this.consentText, other.consentText)) {
return false;
}
if ( ! Objects.equals(this.config, other.config)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return id.hashCode();

View file

@ -117,9 +117,12 @@ public class MapStorageTest extends KeycloakModelTest {
assertClientDoesNotExist(storage2, idMain, kcMain, kc2);
assertClientDoesNotExist(storage2, id1, kc1, kc2);
MapClientEntity clientMain = new MapClientEntityImpl(idMain, realmId);
MapClientEntity client1 = new MapClientEntityImpl(id1, realmId);
MapClientEntity client2 = new MapClientEntityImpl(id2, realmId);
MapClientEntity clientMain = new MapClientEntityImpl(idMain);
clientMain.setRealmId(realmId);
MapClientEntity client1 = new MapClientEntityImpl(id1);
client1.setRealmId(realmId);
MapClientEntity client2 = new MapClientEntityImpl(id2);
client2.setRealmId(realmId);
clientMain = storageMain.create(clientMain);
client1 = storage1.create(client1);