diff --git a/model/build-processor/pom.xml b/model/build-processor/pom.xml new file mode 100644 index 0000000000..0d36e17e87 --- /dev/null +++ b/model/build-processor/pom.xml @@ -0,0 +1,27 @@ + + + + keycloak-model-pom + org.keycloak + 16.0.0-SNAPSHOT + + 4.0.0 + + keycloak-model-build-processor + Keycloak Model Java Annotations and Processor + + + + + junit + junit + test + + + org.hamcrest + hamcrest-all + test + + + + \ No newline at end of file diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEntityImplementations.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEntityImplementations.java new file mode 100644 index 0000000000..f36cf901fb --- /dev/null +++ b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEntityImplementations.java @@ -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 ""; +} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEnumMapFieldType.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEnumMapFieldType.java new file mode 100644 index 0000000000..80a3f9beaf --- /dev/null +++ b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEnumMapFieldType.java @@ -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; +} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java new file mode 100644 index 0000000000..aaed0f2ce2 --- /dev/null +++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java @@ -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 annotations, RoundEnvironment roundEnv) { + for (TypeElement annotation : annotations) { + Set 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> 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 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 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> 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 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 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> me : methodsPerAttribute.entrySet()) { + String enumConstant = toEnumConstant(me.getKey()); + HashSet 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 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 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 parameters) { + return parameters.stream() + .map(p -> p.asType() + " " + p.getSimpleName()) + .collect(Collectors.joining(", ")); + } + + private static final HashSet 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()); + } +} diff --git a/model/map/pom.xml b/model/map/pom.xml index 29548ad219..fce044df6a 100644 --- a/model/map/pom.xml +++ b/model/map/pom.xml @@ -11,6 +11,27 @@ Keycloak Model Map + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.keycloak + keycloak-model-build-processor + ${project.version} + + + + org.keycloak.models.map.processor.GenerateEntityImplementationsProcessor + + + + + + org.bouncycastle @@ -42,6 +63,13 @@ hamcrest-all test + + org.keycloak + keycloak-model-build-processor + ${project.version} + true + + \ No newline at end of file diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java index a6f7bf09f1..e09db4c1f3 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java @@ -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 getWebOrigins() { - return entity.getWebOrigins(); + final Set webOrigins = entity.getWebOrigins(); + return webOrigins == null ? Collections.emptySet() : webOrigins; } @Override @@ -129,7 +134,8 @@ public abstract class MapClientAdapter extends AbstractClientModel getRedirectUris() { - return entity.getRedirectUris(); + final Set redirectUris = entity.getRedirectUris(); + return redirectUris == null ? Collections.emptySet() : redirectUris; } @Override @@ -179,7 +185,8 @@ public abstract class MapClientAdapter extends AbstractClientModel attribute = entity.getAttribute(name); - if (attribute.isEmpty()) return null; + if (attribute == null || attribute.isEmpty()) return null; return attribute.get(0); } @Override public Map getAttributes() { - return entity.getAttributes().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, + final Map> attributes = entity.getAttributes(); + final Map> 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 getAuthenticationFlowBindingOverrides() { - return entity.getAuthenticationFlowBindingOverrides(); + final Map authenticationFlowBindingOverrides = entity.getAuthenticationFlowBindingOverrides(); + return authenticationFlowBindingOverrides == null ? Collections.emptyMap() : authenticationFlowBindingOverrides; } @Override @@ -301,7 +312,8 @@ public abstract class MapClientAdapter extends AbstractClientModel getScopeMappingsStream() { - return this.entity.getScopeMappings().stream() + final Collection 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 scopeMappings = this.entity.getScopeMappings(); + if (id != null && scopeMappings != null && scopeMappings.contains(id)) { return true; } @@ -483,7 +505,8 @@ public abstract class MapClientAdapter extends AbstractClientModel getProtocolMappersStream() { - return entity.getProtocolMappers().stream().distinct(); + final Map protocolMappers = entity.getProtocolMappers(); + return protocolMappers == null ? Stream.empty() : protocolMappers.values().stream().distinct(); } @Override @@ -504,7 +527,8 @@ public abstract class MapClientAdapter extends AbstractClientModel()); } - return entity.addProtocolMapper(pm); + entity.setProtocolMapper(pm.getId(), pm); + return pm; } @Override @@ -519,18 +543,19 @@ public abstract class MapClientAdapter extends AbstractClientModel 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); diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java index af5ae9ad0f..ae80c98baf 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java @@ -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; + + protected AbstractClientEntity() { + this.id = null; + } - ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model); + public AbstractClientEntity(String id) { + this.id = id; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public boolean isUpdated() { + return this.updated; + } + + @Override + public Stream getClientScopes(boolean defaultScope) { + final Map clientScopes = getClientScopes(); + return clientScopes == null ? Stream.empty() : clientScopes.entrySet().stream() + .filter(me -> Objects.equals(me.getValue(), defaultScope)) + .map(Entry::getKey); + } + } + + Map getClientScopes(); + Stream getClientScopes(boolean defaultScope); + void setClientScope(String id, Boolean defaultScope); + void removeClientScope(String id); + + ProtocolMapperModel getProtocolMapper(String id); + Map getProtocolMappers(); + void setProtocolMapper(String id, ProtocolMapperModel mapping); + void removeProtocolMapper(String id); void addRedirectUri(String redirectUri); + Set getRedirectUris(); + void removeRedirectUri(String redirectUri); + void setRedirectUris(Set redirectUris); void addScopeMapping(String id); + void removeScopeMapping(String id); + Collection getScopeMappings(); void addWebOrigin(String webOrigin); + Set getWebOrigins(); + void removeWebOrigin(String webOrigin); + void setWebOrigins(Set webOrigins); - void deleteScopeMapping(String id); - - List getAttribute(String name); - + default List getAttribute(String name) { return getAttributes().get(name); } Map> getAttributes(); + void removeAttribute(String name); + void setAttribute(String name, List values); Map getAuthFlowBindings(); + void setAuthFlowBindings(Map authFlowBindings); String getAuthenticationFlowBindingOverride(String binding); - Map 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 getClientScopes(boolean defaultScope); - String getDescription(); String getManagementUrl(); String getName(); - int getNodeReRegistrationTimeout(); + Integer getNodeReRegistrationTimeout(); - int getNotBefore(); + Integer getNotBefore(); String getProtocol(); - ProtocolMapperModel getProtocolMapperById(String id); - - Collection getProtocolMappers(); - String getRealmId(); - Set getRedirectUris(); - String getRegistrationToken(); String getRootUrl(); Set getScope(); - Collection getScopeMappings(); - String getSecret(); - Set 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 values); - - void setAuthFlowBindings(Map 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 protocolMappers); - void setPublicClient(Boolean publicClient); - void setRedirectUris(Set 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 webOrigins); - - void updateProtocolMapper(String id, ProtocolMapperModel mapping); - } diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityDelegate.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityDelegate.java index 9a2aabcb21..fad13b6414 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityDelegate.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityDelegate.java @@ -30,7 +30,7 @@ public class MapClientEntityDelegate extends MapClientEntityLazyDelegate { } @Override - protected MapClientEntity getDelegate() { + protected MapClientEntity getWriteDelegate() { return delegate; } } diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityImpl.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityImpl.java deleted file mode 100644 index 1131b47bc1..0000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityImpl.java +++ /dev/null @@ -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 redirectUris = new HashSet<>(); - private boolean enabled; - private boolean alwaysDisplayInConsole; - private String clientAuthenticatorType; - private String secret; - private String registrationToken; - private String protocol; - private Map> attributes = new HashMap<>(); - private Map authFlowBindings = new HashMap<>(); - private boolean publicClient; - private boolean fullScopeAllowed; - private boolean frontchannelLogout; - private int notBefore; - private Set scope = new HashSet<>(); - private Set webOrigins = new HashSet<>(); - private Map protocolMappers = new HashMap<>(); - private Map clientScopes = new HashMap<>(); - private Set 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 getRedirectUris() { - return redirectUris; - } - - @Override - public void setRedirectUris(Set 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> getAttributes() { - return attributes; - } - - @Override - public void setAttribute(String name, List values) { - this.updated |= ! Objects.equals(this.attributes.put(name, values), values); - } - - @Override - public Map getAuthFlowBindings() { - return authFlowBindings; - } - - @Override - public void setAuthFlowBindings(Map 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 getScope() { - return scope; - } - - @Override - public void setScope(Set scope) { - this.updated |= ! Objects.equals(this.scope, scope); - this.scope.clear(); - this.scope.addAll(scope); - } - - @Override - public Set getWebOrigins() { - return webOrigins; - } - - @Override - public void setWebOrigins(Set 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 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 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 getAttribute(String name) { - return attributes.getOrDefault(name, Collections.EMPTY_LIST); - } - - @Override - public String getAuthenticationFlowBindingOverride(String binding) { - return this.authFlowBindings.get(binding); - } - - @Override - public Map 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 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 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; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityLazyDelegate.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityLazyDelegate.java index bcfb065c01..95d66c72af 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityLazyDelegate.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntityLazyDelegate.java @@ -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 getAttribute(String name) { - return getDelegate().getAttribute(name); + return getReadDelegate().getAttribute(name); } @Override public Map> getAttributes() { - return getDelegate().getAttributes(); + return getReadDelegate().getAttributes(); } @Override public Map getAuthFlowBindings() { - return getDelegate().getAuthFlowBindings(); + return getReadDelegate().getAuthFlowBindings(); } @Override public String getAuthenticationFlowBindingOverride(String binding) { - return getDelegate().getAuthenticationFlowBindingOverride(binding); + return getReadDelegate().getAuthenticationFlowBindingOverride(binding); } @Override public Map 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 getClientScopes(boolean defaultScope) { - return getDelegate().getClientScopes(defaultScope); + return getReadDelegate().getClientScopes(defaultScope); + } + + @Override + public Map 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 getProtocolMappers() { - return getDelegate().getProtocolMappers(); + public Map 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 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 getScope() { - return getDelegate().getScope(); + return getReadDelegate().getScope(); } @Override public Collection getScopeMappings() { - return getDelegate().getScopeMappings(); + return getReadDelegate().getScopeMappings(); } @Override public String getSecret() { - return getDelegate().getSecret(); + return getReadDelegate().getSecret(); } @Override public Set 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 values) { - getDelegate().setAttribute(name, values); + getWriteDelegate().setAttribute(name, values); } @Override public void setAuthFlowBindings(Map 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 protocolMappers) { - getDelegate().setProtocolMappers(protocolMappers); + getWriteDelegate().setProtocol(protocol); } @Override public void setPublicClient(Boolean publicClient) { - getDelegate().setPublicClient(publicClient); + getWriteDelegate().setPublicClient(publicClient); } @Override public void setRedirectUris(Set 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 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 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(); } } diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java index ed2a06cbf3..576ec6584a 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java @@ -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 diff --git a/model/map/src/main/java/org/keycloak/models/map/common/Serialization.java b/model/map/src/main/java/org/keycloak/models/map/common/Serialization.java index e0c483c9ae..1db0d2f650 100644 --- a/model/map/src/main/java/org/keycloak/models/map/common/Serialization.java +++ b/model/map/src/main/java/org/keycloak/models/map/common/Serialization.java @@ -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) ; diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java index a1a95a5a4b..e28cac4265 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java @@ -62,14 +62,15 @@ public class ConcurrentHashMapKeycloakTransaction checkScopeMappingRole(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { String roleIdS = ensureEqSingleValue(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "role_id", op, values); Function 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 checkGrantedGroupRole(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { String roleIdS = ensureEqSingleValue(GroupModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values); Function 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); } diff --git a/model/pom.xml b/model/pom.xml index 7c19054aea..bbf98ce10e 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -34,5 +34,6 @@ jpa infinispan map + build-processor diff --git a/server-spi/src/main/java/org/keycloak/models/ProtocolMapperModel.java b/server-spi/src/main/java/org/keycloak/models/ProtocolMapperModel.java index 017454c18f..347cbbefc3 100755 --- a/server-spi/src/main/java/org/keycloak/models/ProtocolMapperModel.java +++ b/server-spi/src/main/java/org/keycloak/models/ProtocolMapperModel.java @@ -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(); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/MapStorageTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/MapStorageTest.java index 6533b2753d..0f00ffc20a 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/MapStorageTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/MapStorageTest.java @@ -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);