diff --git a/distribution/api-docs-dist/pom.xml b/distribution/api-docs-dist/pom.xml index a2658fcd8f5..0a010eff8a9 100755 --- a/distribution/api-docs-dist/pom.xml +++ b/distribution/api-docs-dist/pom.xml @@ -50,11 +50,6 @@ - - org.keycloak - keycloak-model-build-processor - ${project.version} - org.jboss.logging jboss-logging-annotations diff --git a/model/build-processor/pom.xml b/model/build-processor/pom.xml deleted file mode 100644 index ef57bdbcc58..00000000000 --- a/model/build-processor/pom.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - keycloak-model-pom - org.keycloak - 999.0.0-SNAPSHOT - - 4.0.0 - - keycloak-model-build-processor - Keycloak Model Java Annotations and Processor - - - - 11 - 11 - 11 - - - \ No newline at end of file diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/CollectionKey.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/CollectionKey.java deleted file mode 100644 index 18fe46ab6de..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/CollectionKey.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023 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; - -/** - * Determines getter of a field which is unique across a set of the same entities within the same context. - * This field can be used as unique key in map-like access to a collection. For example, in the set of - * user consents, this can be client ID. - * - * @author hmlnarik - */ -@Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) -public @interface CollectionKey { - - /** - * Priority of this annotation: The higher the value, the more appropriate the annotation is. - * @return - */ - public int priority() default 0; - -} 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 deleted file mode 100644 index 1a8e3c6d0b4..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateEntityImplementations.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 "org.keycloak.models.map.common.UpdatableEntity.Impl"; -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateHotRodEntityImplementation.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateHotRodEntityImplementation.java deleted file mode 100644 index 9a3c8ae198f..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateHotRodEntityImplementation.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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; - -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) -public @interface GenerateHotRodEntityImplementation { - String implementInterface(); - String inherits() default "org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl"; - - boolean topLevelEntity() default false; - String modelClass() default ""; - - String cacheName() default ""; -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GeneratedFieldType.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GeneratedFieldType.java deleted file mode 100644 index f320ed7424a..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GeneratedFieldType.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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; - -/** - * Specifies the default implementation with a no-args constructor for - * a container property (e.g. a {@code} List} or a {@code Map}). - *

- * Applicable to a setter of a single key from the map (e.g. {@code setAttribute}) or an adder to - * a collection (e.g. {@code addWebOrigin}). This is used to override default type generated by the - * generator in case the entry does not exist yet and a new container needs to be instantiated. - * - * Example: - *

- *  @GeneratedFieldType(HashSet) void addWebOrigin() { ... }
- * 
- * - * @author hmlnarik - */ -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface GeneratedFieldType { - Class value() default Void.class; -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java deleted file mode 100644 index ec03ffeda91..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.CLASS) -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) -public @interface IgnoreForEntityImplementationGenerator { -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/exceptions/CannotMigrateTypeException.java b/model/build-processor/src/main/java/org/keycloak/models/map/exceptions/CannotMigrateTypeException.java deleted file mode 100644 index fd2e8de3e68..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/exceptions/CannotMigrateTypeException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.exceptions; - -import javax.lang.model.type.TypeMirror; -import java.util.Arrays; -import java.util.stream.Collectors; - -public class CannotMigrateTypeException extends RuntimeException { - private final TypeMirror toType; - private final TypeMirror[] fromType; - - public CannotMigrateTypeException(TypeMirror toType, TypeMirror[] fromType) { - this.toType = toType; - this.fromType = fromType; - } - - public String getFormattedMessage() { - return "Cannot migrate [" + Arrays.stream(fromType).map(TypeMirror::toString).collect(Collectors.joining(", ")) + "] to " + toType.toString(); - } - -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java deleted file mode 100644 index 7da921dd2e2..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * 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.CollectionKey; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -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.type.NoType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; -import static org.keycloak.models.map.processor.FieldAccessorType.GETTER; -import static org.keycloak.models.map.processor.Util.getGenericsDeclaration; -import static org.keycloak.models.map.processor.Util.isMapType; -import static org.keycloak.models.map.processor.Util.isSetType; -import static org.keycloak.models.map.processor.Util.singularToPlural; - -@SupportedSourceVersion(SourceVersion.RELEASE_8) -public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor { - - protected static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner"; - protected static final String FQN_ENTITY_FIELD = "org.keycloak.models.map.common.EntityField"; - protected static final String FQN_HAS_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.HasEntityFieldDelegate"; - protected static final String FQN_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.EntityFieldDelegate"; - - protected Elements elements; - protected Types types; - - protected static interface Generator { - void generate(TypeElement e) throws IOException; - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - elements = processingEnv.getElementUtils(); - types = processingEnv.getTypeUtils(); - - for (TypeElement annotation : annotations) { - Set annotatedElements = roundEnv.getElementsAnnotatedWith(annotation); - annotatedElements.stream() - .map(TypeElement.class::cast) - .filter(this::testAnnotationElement) - .forEach(this::processTypeElement); - } - - if (!annotations.isEmpty()) { - afterAnnotationProcessing(); - } - - return true; - } - - public ExecutableElement getCollectionKey(TypeMirror fieldType, ExecutableElement callingMethod) { - if (! Util.isCollectionType(elements.getTypeElement(types.erasure(fieldType).toString()))) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid collection type: " + fieldType, callingMethod); - return null; - } - - TypeMirror collectionType = getGenericsDeclaration(fieldType).get(0); - TypeElement collectionTypeEl = elements.getTypeElement(types.erasure(collectionType).toString()); - - Iterator it = elements.getAllMembers(collectionTypeEl).stream() - .filter(el -> el.getKind() == ElementKind.METHOD) - .filter(el -> el.getAnnotation(CollectionKey.class) != null) - .sorted(Comparator.comparing((Element el) -> el.getAnnotation(CollectionKey.class).priority()).reversed()) - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast) - .iterator(); - - ExecutableElement res = null; - if (it.hasNext()) { - res = it.next(); - if (! res.getParameters().isEmpty() || ! "java.lang.String".equals(res.getReturnType().toString())) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid getter annotated with @CollectionKey in " + res, callingMethod); - } - if (it.hasNext() && it.next().getAnnotation(CollectionKey.class).priority() == res.getAnnotation(CollectionKey.class).priority()) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Multiple getters annotated with @CollectionKey found: " + res + ", " + it.next(), callingMethod); - } - } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "No getters annotated with @CollectionKey in " + collectionType, callingMethod); - } - - return res; - } - - protected boolean testAnnotationElement(TypeElement kind) { return true; } - protected void afterAnnotationProcessing() {} - protected abstract Generator[] getGenerators(); - - private void processTypeElement(TypeElement e) { - for (GenerateEntityImplementationsProcessor.Generator generator : getGenerators()) { - try { - generator.generate(e); - } catch (Exception ex) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not generate implementation for class: " + ex, 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(", "))) -// ); - } - - protected Stream getAllAbstractMethods(TypeElement e) { - return elements.getAllMembers(e).stream() - .filter(el -> el.getKind() == ElementKind.METHOD) - .filter(el -> el.getModifiers().contains(Modifier.ABSTRACT)) - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast); - } - - protected Map> methodsPerAttributeMapping(TypeElement e) { - Map> methodsPerAttribute = getAllAbstractMethods(e) - .filter(Util::isNotIgnored) - .filter(ee -> !(ee.getReceiverType() instanceof NoType && ee.getReceiverType().getKind() != TypeKind.NONE)) - .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(singularToPlural(key))) - .collect(Collectors.toSet()) - .forEach(key -> { - HashSet removed = methodsPerAttribute.remove(key); - methodsPerAttribute.get(singularToPlural(key)).addAll(removed); - }); - - return methodsPerAttribute; - } - - 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"); - } - - protected 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( - Diagnostic.Kind.ERROR, - "Forbidden prefix " + prefix + "... detected, use " + FORBIDDEN_PREFIXES.get(prefix) + "... instead", e - ); - } - return m.group(2); - } - return null; - } - - protected Stream fieldGetters(Map> methodsPerAttribute) { - return methodsPerAttribute.entrySet().stream() - .map(me -> FieldAccessorType.getMethod(GETTER, me.getValue(), me.getKey(), types, determineFieldType(me.getKey(), me.getValue()))) - .filter(Optional::isPresent) - .map(Optional::get); - } - - protected boolean isImmutableFinalType(TypeMirror fieldType) { - return isPrimitiveType(fieldType) - || isBoxedPrimitiveType(fieldType) - || isEnumType(fieldType) - || Objects.equals("java.lang.String", fieldType.toString()); - } - - protected boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) { - List res = getGenericsDeclaration(fieldType); - return isCollection(fieldType) && res.stream().allMatch(this::isImmutableFinalType); - } - - protected boolean isCollection(TypeMirror fieldType) { - TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - switch (typeElement.getQualifiedName().toString()) { - case "java.util.List": - case "java.util.Map": - case "java.util.Set": - case "java.util.Collection": - case "org.keycloak.common.util.MultivaluedHashMap": - return true; - default: - return false; - } - } - - protected String deepClone(TypeMirror fieldType, String parameterName) { - TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - if (isKnownCollectionOfImmutableFinalTypes(fieldType)) { - return parameterName + " == null ? null : " + interfaceToImplementation(typeElement, parameterName); - } else if (isMapType(typeElement)) { - List mapTypes = getGenericsDeclaration(fieldType); - boolean isKeyImmutable = isImmutableFinalType(mapTypes.get(0)); - boolean isValueImmutable = isImmutableFinalType(mapTypes.get(1)); - - return parameterName + " == null ? null : " + parameterName + ".entrySet().stream().collect(" + - "java.util.stream.Collectors.toMap(" + - (isKeyImmutable ? "java.util.Map.Entry::getKey" : "entry -> " + deepClone(mapTypes.get(0), "entry.getKey()")) + - ", " + - (isValueImmutable ? "java.util.Map.Entry::getValue" : "entry -> " + deepClone(mapTypes.get(1), "entry.getValue()")) + - ", (o1, o2) -> o1" + - ", java.util.HashMap::new" + - "))"; - } else if (isCollection(typeElement.asType())) { - TypeMirror collectionType = getGenericsDeclaration(fieldType).get(0); - return parameterName + " == null ? null : " + parameterName + ".stream().map(entry -> " + deepClone(collectionType, "entry") + ").collect(java.util.stream.Collectors.toCollection(" + (isSetType(typeElement) ? "java.util.HashSet::new" : "java.util.LinkedList::new") + "))"; - } - return "deepClone(" + parameterName + ")"; - } - - protected String removeUndefined(TypeMirror fieldType, String parameterName) { - TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - boolean isMapType = isMapType(typeElement); - - return parameterName + (isMapType ? ".values()" : "") + ".removeIf(org.keycloak.models.map.common.UndefinedValuesUtils::isUndefined)"; - } - - protected String isUndefined(String parameterName) { - return "org.keycloak.models.map.common.UndefinedValuesUtils.isUndefined(" + parameterName + ")"; - } - - protected boolean isEnumType(TypeMirror fieldType) { - return types.asElement(fieldType).getKind() == ElementKind.ENUM; - } - - protected boolean isPrimitiveType(TypeMirror fieldType) { - try { - types.getPrimitiveType(fieldType.getKind()); - return true; - } catch (IllegalArgumentException ex) { - return false; - } - } - - protected boolean isBoxedPrimitiveType(TypeMirror fieldType) { - try { - types.unboxedType(fieldType); - return true; - } catch (IllegalArgumentException ex) { - return false; - } - } - - protected String interfaceToImplementation(TypeElement typeElement, String parameter) { - Name parameterTypeQN = typeElement.getQualifiedName(); - switch (parameterTypeQN.toString()) { - case "java.util.List": - case "java.util.Collection": - return "new java.util.LinkedList<>(" + parameter + ")"; - case "java.util.Map": - return "new java.util.HashMap<>(" + parameter + ")"; - case "java.util.Set": - return "new java.util.HashSet<>(" + parameter + ")"; - default: - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement); - return "TODO()"; - } - } - - protected 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(Diagnostic.Kind.ERROR, "Could not determine return type for the field " + fieldName, methods.iterator().next()); - } - return res; - } - - protected void generatedAnnotation(final PrintWriter pw) { - pw.println("@javax.annotation.processing.Generated(\"" + getClass().getName() + "\")"); - } - - protected static class NameFirstComparator implements Comparator { - protected static final Comparator ID_INSTANCE = new NameFirstComparator("id").thenComparing(Comparator.naturalOrder()); - protected static final Comparator GET_ID_INSTANCE = new NameFirstComparator("getId").thenComparing(Comparator.naturalOrder()); - private final String name; - public NameFirstComparator(String name) { - this.name = name; - } - @Override - public int compare(String o1, String o2) { - return Objects.equals(o1, o2) - ? 0 - : name.equalsIgnoreCase(o1) - ? -1 - : name.equalsIgnoreCase(o2) - ? 1 - : 0; - } - - } -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java deleted file mode 100644 index 7a80080f694..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Pattern; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import static org.keycloak.models.map.processor.Util.getGenericsDeclaration; -import static org.keycloak.models.map.processor.Util.pluralToSingular; - -/** - * - * @author hmlnarik - */ -enum FieldAccessorType { - - // Order does matter, see {@link #getMethod} - - GETTER { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName)); - Name methodName = method.getSimpleName(); - return getter.matcher(methodName).matches() && method.getParameters().isEmpty() && types.isSameType(fieldType, method.getReturnType()); - } - }, - SETTER { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String methodName = "set" + fieldName; - return Objects.equals(methodName, method.getSimpleName().toString()) - && method.getParameters().size() == 1 - && types.isSameType(fieldType, method.getParameters().get(0).asType()); - } - }, - COLLECTION_ADD { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String fieldNameSingular = pluralToSingular(fieldName); - String methodName = "add" + fieldNameSingular; - List res = getGenericsDeclaration(fieldType); - return Objects.equals(methodName, method.getSimpleName().toString()) - && res.size() == 1 - && types.isSameType(res.get(0), method.getParameters().get(0).asType()); - } - }, - COLLECTION_DELETE { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String fieldNameSingular = pluralToSingular(fieldName); - String removeFromCollection = "remove" + fieldNameSingular; - List res = getGenericsDeclaration(fieldType); - return Objects.equals(removeFromCollection, method.getSimpleName().toString()) - && method.getParameters().size() == 1 - && types.isSameType(res.get(0), method.getParameters().get(0).asType()); - } - }, - MAP_ADD { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String fieldNameSingular = pluralToSingular(fieldName); - String methodName = "set" + fieldNameSingular; - List res = getGenericsDeclaration(fieldType); - return Objects.equals(methodName, method.getSimpleName().toString()) - && res.size() == 2 - && types.isSameType(res.get(0), method.getParameters().get(0).asType()) - && types.isSameType(res.get(1), method.getParameters().get(1).asType()); - } - }, - MAP_GET { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String fieldNameSingular = pluralToSingular(fieldName); - String methodName = "get" + fieldNameSingular; - List res = getGenericsDeclaration(fieldType); - return Objects.equals(methodName, method.getSimpleName().toString()) - && res.size() == 2 - && types.isSameType(res.get(0), method.getParameters().get(0).asType()); - } - }, - COLLECTION_GET_BY_ID { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String fieldNameSingular = pluralToSingular(fieldName); - String getFromCollection = "get" + fieldNameSingular; - List res = getGenericsDeclaration(fieldType); - return Objects.equals(getFromCollection, method.getSimpleName().toString()) - && method.getParameters().size() == 1 - && res.size() == 1 - && Objects.equals("java.lang.String", method.getParameters().get(0).asType().toString()); - } - }, - COLLECTION_DELETE_BY_ID { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - String fieldNameSingular = pluralToSingular(fieldName); - String removeFromCollection = "remove" + fieldNameSingular; - return Objects.equals(removeFromCollection, method.getSimpleName().toString()) - && method.getParameters().size() == 1 - && Objects.equals("java.lang.String", method.getParameters().get(0).asType().toString()); - } - }, - UNKNOWN /* Must be the last */ { - @Override - public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - return true; - } - - } - ; - - public abstract boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType); - - public static Optional getMethod(FieldAccessorType type, - HashSet methods, String fieldName, Types types, TypeMirror fieldType) { - return methods.stream().filter(ee -> type.is(ee, fieldName, types, fieldType)).findAny(); - } - - public static FieldAccessorType determineType(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) { - for (FieldAccessorType fat : values()) { - if (fat.is(method, fieldName, types, fieldType)) { - return fat; - } - } - return UNKNOWN; - } -} 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 deleted file mode 100644 index 0cf154d86eb..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java +++ /dev/null @@ -1,867 +0,0 @@ -/* - * 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 java.io.IOException; -import java.io.PrintWriter; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -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.type.TypeMirror; -import javax.tools.Diagnostic; -import javax.tools.Diagnostic.Kind; -import javax.tools.JavaFileObject; -import static org.keycloak.models.map.processor.FieldAccessorType.*; -import static org.keycloak.models.map.processor.Util.isSetType; -import static org.keycloak.models.map.processor.Util.methodParameters; -import java.util.Collection; -import java.util.Comparator; -import java.util.IdentityHashMap; -import java.util.Optional; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Stream; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; - -/** - * - * @author hmlnarik - */ -@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateEntityImplementations") -@SupportedSourceVersion(SourceVersion.RELEASE_11) -public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor { - - private static final Collection autogenerated = new TreeSet<>(); - private static final String ID_FIELD_NAME = "Id"; - - private final Generator[] generators = new Generator[] { - new ClonerGenerator(), - new DelegateGenerator(), - new FieldsGenerator(), - new FieldDelegateGenerator(), - new ImplGenerator(), - }; - - @Override - protected void afterAnnotationProcessing() { - if (! autogenerated.isEmpty()) { - try { - JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.common.AutogeneratedClasses"); - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - pw.println("package org.keycloak.models.map.common;"); - - pw.println("import " + FQN_DEEP_CLONER + ".Cloner;"); - pw.println("import " + FQN_DEEP_CLONER + ".DelegateCreator;"); - pw.println("import java.util.function.Function;"); - pw.println("import " + FQN_DEEP_CLONER + ".EntityFieldDelegateCreator;"); - pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName()); - generatedAnnotation(pw); - pw.println("public final class AutogeneratedClasses {"); - pw.println(" public static final java.util.Map, Cloner> CLONERS_WITH_ID = new java.util.HashMap<>();"); - pw.println(" public static final java.util.Map, Cloner> CLONERS_WITHOUT_ID = new java.util.HashMap<>();"); - pw.println(" public static final java.util.Map, DelegateCreator> DELEGATE_CREATORS = new java.util.HashMap<>();"); - pw.println(" public static final java.util.Map, EntityFieldDelegateCreator> ENTITY_FIELD_DELEGATE_CREATORS = new java.util.HashMap<>();"); - pw.println(" public static final java.util.Map, Object> EMPTY_INSTANCES = new java.util.HashMap<>();"); - pw.println(" public static final java.util.Map, EntityField[]> ENTITY_FIELDS = new java.util.HashMap<>();"); - pw.println(" public static final java.util.Map, Function> CONSTRUCTORS_DC = new java.util.HashMap<>();"); - pw.println(" static {"); - autogenerated.forEach(pw::println); - pw.println(" }"); - pw.println("}"); - } - } catch (IOException ex) { - Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - @Override - protected Generator[] getGenerators() { - return this.generators; - } - - @Override - protected boolean testAnnotationElement(TypeElement e) { - if (e.getKind() != ElementKind.INTERFACE) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to an interface", e); - return false; - } - - return true; - } - - protected static String toEnumConstant(String key) { - return key.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase(); - } - - private class FieldsGenerator implements Generator { - - @Override - public void generate(TypeElement e) throws IOException { - Map> methodsPerAttribute = methodsPerAttributeMapping(e); - 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 mapFieldsClassName = className + "Fields"; - String mapSimpleFieldsClassName = simpleClassName + "Fields"; - - JavaFileObject file = processingEnv.getFiler().createSourceFile(mapFieldsClassName); - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - if (packageName != null) { - pw.println("package " + packageName + ";"); - } - - generatedAnnotation(pw); - pw.println("public enum " + mapSimpleFieldsClassName + " implements " + FQN_ENTITY_FIELD + "<" + className + "> {"); - methodsPerAttribute.keySet().stream() - .sorted(NameFirstComparator.ID_INSTANCE) - .forEach(key -> { - pw.println(" " + toEnumConstant(key) + " {"); - printEntityFieldMethods(pw, className, key, methodsPerAttribute.get(key)); - pw.println(" },"); - }); - pw.println("}"); - - autogenerated.add(" ENTITY_FIELDS.put(" + className + ".class, " + mapFieldsClassName + ".values());"); - } - } - - private void printEntityFieldMethods(PrintWriter pw, String className, String fieldName, HashSet methods) { - TypeMirror fieldType = determineFieldType(fieldName, methods); - pw.println(" public static final String FIELD_NAME = \"" + fieldName + "\";"); - pw.println(" public static final String FIELD_NAME_DASHED = \"" + fieldName.replaceAll("([^_A-Z])([A-Z])", "$1-$2").toLowerCase() + "\";"); - pw.println(" public static final String FIELD_NAME_CAMEL_CASE = \"" + fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1) + "\";"); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class getFieldClass() {"); - pw.println(" return " + types.erasure(fieldType) + ".class;"); - pw.println(" }"); - pw.println(" @Override public String getName() {"); - pw.println(" return FIELD_NAME;"); - pw.println(" }"); - pw.println(" @Override public String getNameDashed() {"); - pw.println(" return FIELD_NAME_DASHED;"); - pw.println(" }"); - pw.println(" @Override public String getNameCamelCase() {"); - pw.println(" return FIELD_NAME_CAMEL_CASE;"); - pw.println(" }"); - - FieldAccessorType.getMethod(FieldAccessorType.GETTER, methods, fieldName, types, fieldType).ifPresent(method -> { - if (Util.isCollectionType((TypeElement) types.asElement(types.erasure(fieldType)))) { - TypeMirror firstParameterType = Util.getGenericsDeclaration(method.getReturnType()).get(0); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class getCollectionElementClass() {"); - pw.println(" return " + types.erasure(firstParameterType) + ".class;"); - pw.println(" }"); - - } else if (Util.isMapType((TypeElement) types.asElement(types.erasure(fieldType)))) { - TypeMirror firstParameterType = Util.getGenericsDeclaration(method.getReturnType()).get(0); - TypeMirror secondParameterType = Util.getGenericsDeclaration(method.getReturnType()).get(1); - - pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class getMapKeyClass() {"); - pw.println(" return " + types.erasure(firstParameterType) + ".class;"); - pw.println(" }"); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class getMapValueClass() {"); - pw.println(" return " + types.erasure(secondParameterType) + ".class;"); - pw.println(" }"); - } - }); - - for (ExecutableElement ee : methods) { - FieldAccessorType fat = FieldAccessorType.determineType(ee, fieldName, types, fieldType); - printMethodBody(pw, fat, ee, className, fieldType); - } - } - - private void printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String className, TypeMirror fieldType) { - TypeMirror firstParameterType = method.getParameters().isEmpty() - ? types.getNullType() - : method.getParameters().get(0).asType(); - - switch (accessorType) { - case GETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " get(" + className + " e) {"); - pw.println(" return (" + fieldType + ") e." + method.getSimpleName() + "();"); - pw.println(" }"); - return; - case SETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public void set(" + className + " e, T value) {"); - pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);"); - pw.println(" }"); - return; - case COLLECTION_ADD: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public void collectionAdd(" + className + " e, T value) {"); - pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);"); - pw.println(" }"); - return; - case COLLECTION_DELETE: - { - String returnType = method.getReturnType().getKind() == TypeKind.VOID ? "Void" : method.getReturnType().toString(); - TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - if (Util.isMapType(fieldTypeElement)) { - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + returnType + " mapRemove(" + className + " e, K p0) {"); - } else { - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + returnType + " collectionRemove(" + className + " e, T p0) {"); - } - if (method.getReturnType().getKind() == TypeKind.VOID) { - pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") p0); return null;"); - } else { - pw.println(" return e." + method.getSimpleName() + "((" + firstParameterType + ") p0);"); - } - pw.println(" }"); - return; - } - case COLLECTION_DELETE_BY_ID: - { - String returnType = method.getReturnType().getKind() == TypeKind.VOID ? "Void" : method.getReturnType().toString(); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + returnType + " mapRemove(" + className + " e, K p0) {"); - if (method.getReturnType().getKind() == TypeKind.VOID) { - pw.println(" e." + method.getSimpleName() + "((String) p0); return null;"); - } else { - pw.println(" return e." + method.getSimpleName() + "((String) p0);"); - } - pw.println(" }"); - return; - } - case COLLECTION_GET_BY_ID: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " mapGet(" + className + " e, K key) {"); - pw.println(" return e." + method.getSimpleName() + "((" + firstParameterType + ") key);"); - pw.println(" }"); - return; - case MAP_ADD: - TypeMirror secondParameterType = method.getParameters().get(1).asType(); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public void mapPut(" + className + " e, K key, T value) {"); - pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") key, (" + secondParameterType + ") value);"); - pw.println(" }"); - return; - case MAP_GET: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " mapGet(" + className + " e, K key) {"); - pw.println(" return (" + method.getReturnType() + ") e." + method.getSimpleName() + "((" + firstParameterType + ") key);"); - pw.println(" }"); - } - } - } - - private class ImplGenerator implements Generator { - - @Override - public void generate(TypeElement e) throws IOException { - Map> methodsPerAttribute = methodsPerAttributeMapping(e); - GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class); - TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits()); - if (parentTypeElement == null) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to find type " + an.inherits() + " for inherits parameter for annotation " + GenerateEntityImplementations.class.getTypeName(), e); - } - final List allParentMembers = 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"; - boolean hasId = methodsPerAttribute.containsKey(ID_FIELD_NAME) || allParentMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString())); - boolean hasDeepClone = allParentMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString())); - boolean needsDeepClone = fieldGetters(methodsPerAttribute) - .map(ExecutableElement::getReturnType) - .anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType)); - boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone; - - JavaFileObject file = processingEnv.getFiler().createSourceFile(mapImplClassName); - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - if (packageName != null) { - pw.println("package " + packageName + ";"); - } - - pw.println("import java.util.Objects;"); - pw.println("import java.util.Optional;"); - pw.println("import " + FQN_DEEP_CLONER + ";"); - pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName()); - generatedAnnotation(pw); - pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {"); - - pw.println(" " - + "private " - + mapSimpleClassName + "() { this(DeepCloner.DUMB_CLONER); } // Nullary constructor only for Jackson deserialization" - ); - pw.println(" " - + "public " - + mapSimpleClassName + "(DeepCloner cloner) { super(); " + (!usingGeneratedCloner ? "" : "this.cloner = cloner;") + "}" - ); - - // equals, hashCode, toString - pw.println(" @Override public boolean equals(Object o) {"); - pw.println(" if (o == this) return true; "); - pw.println(" if (! (o instanceof " + mapSimpleClassName + ")) return false; "); - pw.println(" " + mapSimpleClassName + " other = (" + mapSimpleClassName + ") o; "); - pw.println(" return " - + fieldGetters(methodsPerAttribute) - .map(ExecutableElement::getSimpleName) - .map(Name::toString) - .sorted(NameFirstComparator.GET_ID_INSTANCE) - .map(v -> "Objects.equals(" + v + "(), other." + v + "())") - .collect(Collectors.joining("\n && ")) - + ";"); - pw.println(" }"); - pw.println(" @Override public int hashCode() {"); - pw.println(" return " - + (hasId - ? "(getId() == null ? super.hashCode() : getId().hashCode())" - : "Objects.hash(" - + fieldGetters(methodsPerAttribute) - .filter(ee -> isImmutableFinalType(ee.getReturnType())) - .map(ExecutableElement::getSimpleName) - .map(Name::toString) - .sorted(NameFirstComparator.GET_ID_INSTANCE) - .map(v -> v + "()") - .collect(Collectors.joining(",\n ")) - + ")") - + ";"); - pw.println(" }"); - pw.println(" @Override public String toString() {"); - pw.println(" return String.format(\"%s@%08x\", " + (hasId ? "getId()" : "\"" + mapSimpleClassName + "\"" ) + ", System.identityHashCode(this));"); - pw.println(" }"); - - // deepClone - if (usingGeneratedCloner) { - pw.println(" private final DeepCloner cloner;"); - pw.println(" public V deepClone(V obj) {"); - pw.println(" return cloner.from(obj);"); - pw.println(" }"); - } - - // fields, getters, setters - methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, NameFirstComparator.ID_INSTANCE)).forEach(me -> { - HashSet methods = me.getValue(); - TypeMirror fieldType = determineFieldType(me.getKey(), methods); - if (fieldType == null) { - return; - } - - pw.println(""); - pw.println(" private " + fieldType + " f" + me.getKey() + ";"); - - for (ExecutableElement method : methods) { - FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType); - Optional parentMethod = Util.findParentMethodImplementation(allParentMembers, method); - - if (parentMethod.isPresent()) { - processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method); - } else if (fat == FieldAccessorType.UNKNOWN || ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) { - processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method); - } - } - }); - - // Read-only class overrides setters to be no-op - pw.println(" public static class Empty " + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {"); - pw.println(" public static final Empty INSTANCE = new Empty();"); - methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, NameFirstComparator.ID_INSTANCE)) - .map(Map.Entry::getValue) - .flatMap(Collection::stream) - .forEach(ee -> { - pw.println(" @Override " - + ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" ")) - + " " + ee.getReturnType() - + " " + ee.getSimpleName() - + "(" + methodParameters(ee.getParameters()) + ") {"); - if (ee.getReturnType().getKind() == TypeKind.VOID) { - pw.println(" }"); - } else { - pw.println(" return null;"); - pw.println(" }"); - } - }); - elements.getAllMembers(e).stream() - .filter(ee -> ee.getSimpleName().contentEquals("isUpdated")) - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast) - .filter(ee -> ee.getReturnType().getKind() == TypeKind.BOOLEAN) - .forEach(ee -> { - pw.println(" @Override " - + ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" ")) - + " " + ee.getReturnType() - + " " + ee.getSimpleName() - + "(" + methodParameters(ee.getParameters()) + ") {"); - pw.println(" return false;"); - pw.println(" }"); - }); - pw.println(" }"); - - autogenerated.add(" EMPTY_INSTANCES.put(" + className + ".class, " + mapImplClassName + ".Empty.INSTANCE);"); - autogenerated.add(" CONSTRUCTORS_DC.put(" + className + ".class, " + mapImplClassName + "::new);"); - - pw.println("}"); - } - } - - private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) { - TypeMirror firstParameterType = method.getParameters().isEmpty() - ? types.getNullType() - : method.getParameters().get(0).asType(); - TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - - switch (accessorType) { - case GETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {"); - pw.println(" return " + fieldName + ";"); - pw.println(" }"); - return true; - case SETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - if (! isImmutableFinalType(fieldType)) { - pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";"); - } - if (isCollection(firstParameterType)) { - pw.println(" if (p0 != null) {"); - pw.println(" " + removeUndefined(firstParameterType, "p0") + ";"); - pw.println(" if (" + isUndefined("p0") + ") p0 = null;"); - pw.println(" }"); - } - pw.println(" updated |= ! Objects.equals(" + fieldName + ", p0);"); - pw.println(" " + fieldName + " = p0;"); - pw.println(" }"); - return true; - case COLLECTION_ADD: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - if (! isImmutableFinalType(firstParameterType)) { - pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";"); - } - if (isCollection(firstParameterType)) { - pw.println(" if (p0 != null) " + removeUndefined(firstParameterType, "p0") + ";"); - } - pw.println(" if (" + isUndefined("p0") + ") return;"); - pw.println(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }"); - if (isSetType(typeElement)) { - pw.println(" updated |= " + fieldName + ".add(p0);"); - } else { - pw.println(" " + fieldName + ".add(p0);"); - pw.println(" updated = true;"); - } - pw.println(" }"); - return true; - case COLLECTION_DELETE: - { - boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID; - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - pw.println(" if (" + fieldName + " == null) { return" + (needsReturn ? " false" : "") + "; }"); - pw.println(" boolean removed = " + fieldName + ".remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";"); - pw.println(" updated |= removed;"); - if (needsReturn) pw.println(" return removed;"); - pw.println(" }"); - return true; - } - case COLLECTION_DELETE_BY_ID: - { - boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID; - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {"); - pw.println(" boolean removed = " + fieldName + " != null && " + fieldName + ".removeIf(o -> Objects.equals(o." + getCollectionKey(fieldType, method) + ", p0));"); - pw.println(" updated |= removed;"); - if (needsReturn) pw.println(" return removed;"); - pw.println(" }"); - return true; - } - case COLLECTION_GET_BY_ID: - { - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {"); - pw.println(" if (" + fieldName + " == null || " + fieldName + ".isEmpty()) return Optional.empty();"); - pw.println(" return " + fieldName + ".stream().filter(o -> Objects.equals(o." + getCollectionKey(fieldType, method) + ", p0)).findFirst();"); - pw.println(" }"); - return true; - } - case MAP_ADD: - TypeMirror secondParameterType = method.getParameters().get(1).asType(); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {"); - if (! isImmutableFinalType(secondParameterType)) { - pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";"); - } - if (isCollection(secondParameterType)) { - pw.println(" if (p1 != null) " + removeUndefined(secondParameterType, "p1") + ";"); - } - pw.println(" boolean valueUndefined = " + isUndefined("p1") + ";"); - pw.println(" if (valueUndefined) { if (" + fieldName + " != null) { updated |= " + fieldName + ".remove(p0) != null; } return; }"); - pw.println(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }"); - - pw.println(" Object v = " + fieldName + ".put(p0, p1);"); - pw.println(" updated |= ! Objects.equals(v, p1);"); - pw.println(" }"); - return true; - case MAP_GET: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - pw.println(" return " + fieldName + " == null ? null : " + fieldName + ".get(p0);"); - pw.println(" }"); - return true; - } - - return false; - } - } - - private class FieldDelegateGenerator implements Generator { - - @Override - public void generate(TypeElement e) throws IOException { - Map> methodsPerAttribute = methodsPerAttributeMapping(e); - 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 mapClassName = className + "FieldDelegate"; - String mapSimpleClassName = simpleClassName + "FieldDelegate"; - String fieldsClassName = className + "Fields"; - - GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class); - TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits()); - if (parentTypeElement == null) { - return; - } - - JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName); - IdentityHashMap m2field = new IdentityHashMap<>(); - methodsPerAttribute.forEach((f, s) -> s.forEach(m -> m2field.put(m, f))); // Create reverse map - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - if (packageName != null) { - pw.println("package " + packageName + ";"); - } - - generatedAnnotation(pw); - pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + ", " + FQN_HAS_ENTITY_FIELD_DELEGATE + "<" + className + ">" + " {"); - pw.println(" private final " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate;"); - pw.println(" public " + mapSimpleClassName + "(" + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate) {"); - pw.println(" this.entityFieldDelegate = entityFieldDelegate;"); - pw.println(" }"); - pw.println(" public " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> getEntityFieldDelegate() {"); - pw.println(" return this.entityFieldDelegate;"); - pw.println(" }"); - - pw.println(" @Override public boolean isUpdated() {"); - pw.println(" return entityFieldDelegate.isUpdated();"); - pw.println(" }"); - - pw.println(" @Override public void markUpdatedFlag() {"); - pw.println(" entityFieldDelegate.markUpdatedFlag();"); - pw.println(" }"); - - pw.println(" @Override public void clearUpdatedFlag() {"); - pw.println(" entityFieldDelegate.clearUpdatedFlag();"); - pw.println(" }"); - - pw.println(" @Override public String toString() {"); - pw.println(" return \"%\" + String.valueOf(entityFieldDelegate);"); - pw.println(" }"); - - getAllAbstractMethods(e) - .forEach(ee -> { - String originalField = m2field.get(ee); - if (originalField == null) { - return; - } - TypeMirror fieldType = determineFieldType(originalField, methodsPerAttribute.get(originalField)); - String field = fieldsClassName + "." + toEnumConstant(originalField); - - FieldAccessorType fat = FieldAccessorType.determineType(ee, originalField, types, fieldType); - printMethodBody(pw, fat, ee, field, fieldType); - }); - - autogenerated.add(" ENTITY_FIELD_DELEGATE_CREATORS.put(" + className + ".class, (EntityFieldDelegateCreator<" + className + ">) " + mapClassName + "::new);"); - - pw.println("}"); - } - } - - private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) { - TypeMirror firstParameterType = method.getParameters().isEmpty() - ? types.getNullType() - : method.getParameters().get(0).asType(); - - switch (accessorType) { - case GETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {"); - pw.println(" return (" + fieldType + ") entityFieldDelegate.get(" + fieldName + ");"); - pw.println(" }"); - return true; - case SETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - pw.println(" entityFieldDelegate.set(" + fieldName + ", p0);"); - pw.println(" }"); - return true; - case COLLECTION_ADD: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - pw.println(" entityFieldDelegate.collectionAdd(" + fieldName + ", p0);"); - pw.println(" }"); - return true; - case COLLECTION_DELETE: - { - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - String removeMethod = Util.isMapType(fieldTypeElement) ? "mapRemove" : "collectionRemove"; - if (method.getReturnType().getKind() == TypeKind.VOID) { - pw.println(" entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);"); - } else { - pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);"); - } - pw.println(" }"); - return true; - } - case COLLECTION_DELETE_BY_ID: - { - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {"); - if (method.getReturnType().getKind() == TypeKind.VOID) { - pw.println(" entityFieldDelegate.mapRemove(" + fieldName + ", p0);"); - } else { - pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate.mapRemove(" + fieldName + ", p0);"); - } - pw.println(" }"); - return true; - } - case MAP_ADD: - TypeMirror secondParameterType = method.getParameters().get(1).asType(); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {"); - pw.println(" entityFieldDelegate.mapPut(" + fieldName + ", p0, p1);"); - pw.println(" }"); - return true; - case COLLECTION_GET_BY_ID: - case MAP_GET: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate.mapGet(" + fieldName + ", p0);"); - pw.println(" }"); - return true; - } - - return false; - } - } - - private class DelegateGenerator implements Generator { - @Override - public void generate(TypeElement e) throws IOException { - Map> methodsPerAttribute = methodsPerAttributeMapping(e); - 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 mapClassName = className + "Delegate"; - String mapSimpleClassName = simpleClassName + "Delegate"; - String fieldsClassName = className + "Fields"; - - GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class); - TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits()); - if (parentTypeElement == null) { - return; - } - - JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName); - IdentityHashMap m2field = new IdentityHashMap<>(); - methodsPerAttribute.forEach((f, s) -> s.forEach(m -> m2field.put(m, f))); // Create reverse map - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - if (packageName != null) { - pw.println("package " + packageName + ";"); - } - - generatedAnnotation(pw); - pw.println("public class " + mapSimpleClassName + " implements " + className + ", org.keycloak.models.map.common.delegate.HasDelegateProvider<" + className + "> {"); - pw.println(" private final org.keycloak.models.map.common.delegate.DelegateProvider<" + className + "> delegateProvider;"); - pw.println(" public " + mapSimpleClassName + "(org.keycloak.models.map.common.delegate.DelegateProvider<" + className + "> delegateProvider) {"); - pw.println(" this.delegateProvider = delegateProvider;"); - pw.println(" }"); - pw.println(" public org.keycloak.models.map.common.delegate.DelegateProvider<" + className + "> getDelegateProvider() {"); - pw.println(" return this.delegateProvider;"); - pw.println(" }"); - - pw.println(" @Override public String toString() {"); - pw.println(" return \"/\" + String.valueOf(this.delegateProvider);"); - pw.println(" }"); - - getAllAbstractMethods(e) - .forEach(ee -> { - printMethodHeader(pw, ee); - String field = m2field.get(ee); - field = field == null ? "null" : fieldsClassName + "." + toEnumConstant(field); - if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) { - pw.println(" return delegateProvider.isUpdated();"); - } else if (ee.getReturnType().getKind() == TypeKind.VOID) { // write operation - pw.println(" delegateProvider.getDelegate(false, " - + Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", ")) - + ")." + ee.getSimpleName() + "(" - + ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", ")) - + ");"); - } else { - pw.println(" return delegateProvider.getDelegate(true, " - + Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", ")) - + ")." + ee.getSimpleName() + "(" - + ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", ")) - + ");"); - } - pw.println(" }"); - }); - - pw.println("}"); - - autogenerated.add(" DELEGATE_CREATORS.put(" + className + ".class, (DelegateCreator<" + className + ">) " + mapClassName + "::new);"); - } - } - } - - protected void printMethodHeader(final PrintWriter pw, ExecutableElement ee) { - pw.println(" @Override " - + ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" ")) - + " " + ee.getReturnType() - + " " + ee.getSimpleName() - + "(" + methodParameters(ee.getParameters()) + ") {"); - } - - private class ClonerGenerator implements Generator { - - @Override - public void generate(TypeElement e) throws IOException { - Map> methodsPerAttribute = methodsPerAttributeMapping(e); - 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 clonerImplClassName = className + "Cloner"; - String clonerSimpleClassName = simpleClassName + "Cloner"; - - JavaFileObject enumFile = processingEnv.getFiler().createSourceFile(clonerImplClassName); - 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 " + FQN_DEEP_CLONER + ";"); - pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName()); - generatedAnnotation(pw); - pw.println("public class " + clonerSimpleClassName + " {"); - - if (methodsPerAttribute.containsKey(ID_FIELD_NAME)) { - pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {"); - - // If the entity has an ID, set the ID first and then set all other attributes. - // This was important when working with Jpa storage as the ID is the one field needed to persist an entity. - HashSet idMethods = methodsPerAttribute.get(ID_FIELD_NAME); - TypeMirror idFieldType = determineFieldType(ID_FIELD_NAME, idMethods); - cloneField(e, ID_FIELD_NAME, idMethods, idFieldType, pw); - - pw.println(" return deepCloneNoId(original, target);"); - pw.println(" }"); - - autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);"); - - pw.println(" public static " + className + " deepCloneNoId(" + className + " original, " + className + " target) {"); - - methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> { - final String fieldName = me.getKey(); - HashSet methods = me.getValue(); - TypeMirror fieldType = determineFieldType(fieldName, methods); - if (fieldType == null || ID_FIELD_NAME.equals(fieldName)) { - return; - } - - cloneField(e, fieldName, methods, fieldType, pw); - }); - pw.println(" target.clearUpdatedFlag();"); - pw.println(" return target;"); - pw.println(" }"); - - autogenerated.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);"); - } else { - pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {"); - - methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> { - final String fieldName = me.getKey(); - HashSet methods = me.getValue(); - TypeMirror fieldType = determineFieldType(fieldName, methods); - if (fieldType == null) { - return; - } - - cloneField(e, fieldName, methods, fieldType, pw); - }); - pw.println(" target.clearUpdatedFlag();"); - pw.println(" return target;"); - pw.println(" }"); - - autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);"); - } - pw.println("}"); - } - } - - private void cloneField(TypeElement e, final String fieldName, HashSet methods, TypeMirror fieldType, final PrintWriter pw) { - ExecutableElement getter = FieldAccessorType.getMethod(GETTER, methods, fieldName, types, fieldType).orElse(null); - if (getter == null) { - processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine getter for " + fieldName + " property"); - return; - } - - Optional setter = FieldAccessorType.getMethod(SETTER, methods, fieldName, types, fieldType); - Optional addToCollection = FieldAccessorType.getMethod(COLLECTION_ADD, methods, fieldName, types, fieldType); - Optional updateMap = FieldAccessorType.getMethod(MAP_ADD, methods, fieldName, types, fieldType); - - if (setter.isPresent()) { - final Name setterName = setter.get().getSimpleName(); - // Setter always deep-clones whatever comes from the original, so we don't clone the value here. - pw.println(" target." + setterName + "(original." + getter.getSimpleName() + "());"); - } else if (addToCollection.isPresent()) { - pw.println(" if (original." + getter.getSimpleName() + "() != null) {"); - pw.println(" original." + getter.getSimpleName() + "().forEach(target::" + addToCollection.get().getSimpleName() + ");"); - pw.println(" }"); - } else if (updateMap.isPresent()) { - pw.println(" if (original." + getter.getSimpleName() + "() != null) {"); - pw.println(" original." + getter.getSimpleName() + "().forEach(target::" + updateMap.get().getSimpleName() + ");"); - pw.println(" }"); - } else { - processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine way to clone " + fieldName + " property", e); - } - } - } -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateHotRodEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateHotRodEntityImplementationsProcessor.java deleted file mode 100644 index c0d8d6b386e..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateHotRodEntityImplementationsProcessor.java +++ /dev/null @@ -1,711 +0,0 @@ -/* - * 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.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.exceptions.CannotMigrateTypeException; - -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.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; -import javax.tools.JavaFileObject; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalInt; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static org.keycloak.models.map.processor.Util.getGenericsDeclaration; -import static org.keycloak.models.map.processor.Util.isMapType; -import static org.keycloak.models.map.processor.Util.isSetType; -import static org.keycloak.models.map.processor.Util.methodParameters; - -@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation") -@SupportedSourceVersion(SourceVersion.RELEASE_8) -public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor { - - private static Collection autogenerated = new TreeSet<>(); - - @Override - protected Generator[] getGenerators() { - return new Generator[] { new HotRodGettersAndSettersDelegateGenerator(), new HotRodEntityDescriptorGenerator() }; - } - - @Override - protected void afterAnnotationProcessing() { - if (! autogenerated.isEmpty()) { - try { - JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors"); - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - pw.println("package org.keycloak.models.map.storage.hotRod.common;"); - - pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateHotRodEntityImplementationsProcessor.class.getSimpleName()); - generatedAnnotation(pw); - pw.println("public final class AutogeneratedHotRodDescriptors {"); - pw.println(" public static final java.util.Map, HotRodEntityDescriptor> ENTITY_DESCRIPTOR_MAP = new java.util.HashMap<>();"); - pw.println(" static {"); - autogenerated.forEach(pw::println); - pw.println(" }"); - pw.println("}"); - } - } catch (IOException ex) { - Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - private class HotRodGettersAndSettersDelegateGenerator implements Generator { - - private static final String ENTITY_VARIABLE = "hotRodEntity"; - private String hotRodSimpleClassName; - private TypeElement generalHotRodDelegate; - private TypeElement abstractEntity; - private TypeElement abstractHotRodEntity; - private TypeElement enumWithStableId; - private TypeElement hotRodUtils; - - @Override - public void generate(TypeElement e) throws IOException { - GenerateHotRodEntityImplementation hotRodAnnotation = e.getAnnotation(GenerateHotRodEntityImplementation.class); - String interfaceClass = hotRodAnnotation.implementInterface(); - if (interfaceClass == null || interfaceClass.isEmpty()) return; - TypeElement parentClassElement = elements.getTypeElement(hotRodAnnotation.inherits()); - if (parentClassElement == null) return; - boolean parentClassHasGeneric = !getGenericsDeclaration(parentClassElement.asType()).isEmpty(); - - - TypeElement parentInterfaceElement = elements.getTypeElement(interfaceClass); - if (parentInterfaceElement == null) return; - Map> methodsPerAttribute = methodsPerAttributeMapping(parentInterfaceElement); - - - final List allMembers = elements.getAllMembers(parentClassElement); - 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 hotRodImplClassName = className + "Delegate"; - hotRodSimpleClassName = simpleClassName + "Delegate"; - generalHotRodDelegate = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate"); - abstractEntity = elements.getTypeElement("org.keycloak.models.map.common.AbstractEntity"); - abstractHotRodEntity = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity"); - enumWithStableId = elements.getTypeElement("org.keycloak.util.EnumWithStableIndex"); - hotRodUtils = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils"); - - boolean hasDeepClone = allMembers.stream() - .filter(el -> el.getKind() == ElementKind.METHOD && !el.getModifiers().contains(Modifier.ABSTRACT)).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString())); - boolean needsDeepClone = fieldGetters(methodsPerAttribute) - .map(ExecutableElement::getReturnType) - .anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType)); - boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone; - boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString())); - boolean hasFieldId = elements.getAllMembers(e).stream() - .filter(VariableElement.class::isInstance) - .map(VariableElement.class::cast) - .anyMatch(variableElement -> variableElement.getSimpleName().toString().equals("id")); - - JavaFileObject file = processingEnv.getFiler().createSourceFile(hotRodImplClassName); - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - if (packageName != null) { - pw.println("package " + packageName + ";"); - } - - pw.println("import java.util.Objects;"); - pw.println("import " + FQN_DEEP_CLONER + ";"); - pw.println("import java.util.Optional;"); - pw.println("import java.util.stream.Collectors;"); - pw.println(); - pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateHotRodEntityImplementationsProcessor.class.getSimpleName()); - generatedAnnotation(pw); - pw.println("public class " + hotRodSimpleClassName - + " extends " - + parentClassElement.getQualifiedName().toString() + (parentClassHasGeneric ? "<" + e.getQualifiedName().toString() + ">" : "") - + " implements " - + parentInterfaceElement.getQualifiedName().toString() - + " {"); - pw.println(); - pw.println(" private final " + className + " " + ENTITY_VARIABLE + ";"); - pw.println(); - - // Constructors - allMembers.stream() - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast) - .filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR) - .forEach((ExecutableElement ee) -> { - // Create constructor and initialize cloner to DUMB_CLONER if necessary - if (usingGeneratedCloner) { - pw.println(" /**"); - pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead"); - pw.println(" */"); - } - pw.println(" " - + ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" ")) - + " " + hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {" - ); - pw.println(" super(" + ee.getParameters() + ");"); - if (usingGeneratedCloner) pw.println(" this.cloner = DeepCloner.DUMB_CLONER;"); - pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();"); - pw.println(" }"); - }); - - // Add constructor for setting HotRodEntity - if (usingGeneratedCloner) { - pw.println(" /**"); - pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead"); - pw.println(" */"); - } - pw.println(" " + - "public " + hotRodSimpleClassName + "(" + className + " " + ENTITY_VARIABLE + ") {" - ); - pw.println(" java.util.Objects.requireNonNull(" + ENTITY_VARIABLE + ");"); - pw.println(" this." + ENTITY_VARIABLE + " = " + ENTITY_VARIABLE + ";"); - if (usingGeneratedCloner) { - pw.println(" this.cloner = DeepCloner.DUMB_CLONER;"); - } - pw.println(" }"); - - pw.println(" public " + hotRodSimpleClassName + "(DeepCloner cloner) {"); - pw.println(" super();"); - pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();"); - if (usingGeneratedCloner) pw.println(" this.cloner = cloner;"); - pw.println(" }"); - - // equals, hashCode, toString - pw.println(" @Override public boolean equals(Object o) {"); - pw.println(" if (o == this) return true; "); - pw.println(" if (! (o instanceof " + hotRodSimpleClassName + ")) return false; "); - pw.println(" " + hotRodSimpleClassName + " other = (" + hotRodSimpleClassName + ") o; "); - pw.println(" return " - + fieldGetters(methodsPerAttribute) - .map(ExecutableElement::getSimpleName) - .map(Name::toString) - .sorted(NameFirstComparator.GET_ID_INSTANCE) - .map(v -> "Objects.equals(" + v + "(), other." + v + "())") - .collect(Collectors.joining("\n && ")) - + ";"); - pw.println(" }"); - pw.println(" @Override public int hashCode() {"); - pw.println(" return " - + (hasId - ? "(getId() == null ? super.hashCode() : getId().hashCode())" - : "Objects.hash(" - + fieldGetters(methodsPerAttribute) - .filter(ee -> isImmutableFinalType(ee.getReturnType())) - .map(ExecutableElement::getSimpleName) - .map(Name::toString) - .sorted(GenerateEntityImplementationsProcessor.NameFirstComparator.GET_ID_INSTANCE) - .map(v -> v + "()") - .collect(Collectors.joining(",\n ")) - + ")") - + ";"); - pw.println(" }"); - pw.println(" @Override public String toString() {"); - pw.println(" return String.format(\"%s@%08x\", " + (hasId ? "getId()" : "\"" + hotRodSimpleClassName + "\"" ) + ", System.identityHashCode(this));"); - pw.println(" }"); - - pw.println(" public static boolean entityEquals(Object o1, Object o2) {"); - pw.println(" if (!(o1 instanceof " + className + ")) return false;"); - pw.println(" if (!(o2 instanceof " + className + ")) return false;"); - - pw.println(" if (o1 == o2) return true;"); - - pw.println(" " + className + " e1 = (" + className + ") o1;"); - pw.println(" " + className + " e2 = (" + className + ") o2;"); - - pw.print(" return "); - pw.println(elements.getAllMembers(e).stream() - .filter(Util::isNotIgnored) - .filter(VariableElement.class::isInstance) - .map(VariableElement.class::cast) - .map(var -> "Objects.equals(e1." + var.getSimpleName().toString() + ", e2." + var.getSimpleName().toString() + ")") - .collect(Collectors.joining("\n && "))); - pw.println(" ;"); - pw.println(" }"); - - pw.println(" public static int entityHashCode(" + className + " e) {"); - pw.println(" return " - + (hasFieldId - ? "(e.id == null ? Objects.hash(e) : e.id.hashCode())" - : "Objects.hash(" - + elements.getAllMembers(e).stream() - .filter(VariableElement.class::isInstance) - .map(VariableElement.class::cast) - .map(var -> "e." + var.getSimpleName().toString()) - .collect(Collectors.joining(",\n ")) - + ")") - + ";" - ); - pw.println(" }"); - - // deepClone - if (! hasDeepClone && needsDeepClone) { - pw.println(" private final DeepCloner cloner;"); - pw.println(" public V deepClone(V obj) {"); - pw.println(" return cloner.from(obj);"); - pw.println(" }"); - } - - // getters, setters - methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, GenerateEntityImplementationsProcessor.NameFirstComparator.ID_INSTANCE)).forEach(me -> { - HashSet methods = me.getValue(); - TypeMirror fieldType = determineFieldType(me.getKey(), methods); - if (fieldType == null) { - return; - } - - // Determine HotRod entity field name by changing case of first letter - char[] c = me.getKey().toCharArray(); - c[0] = Character.toLowerCase(c[0]); - String hotRodEntityFieldName = new String(c); - - // Find corresponding variable in HotRod*Entity - Optional hotRodVariable = elements.getAllMembers(e).stream() - .filter(VariableElement.class::isInstance) - .map(VariableElement.class::cast) - .filter(variableElement -> variableElement.getSimpleName().toString().equals(hotRodEntityFieldName)) - .findFirst(); - - // Implement each method - for (ExecutableElement method : methods) { - FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType); - - // Check if the parent class implements the method already - Optional parentMethod = 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)) - .findAny(); - - try { - if (parentMethod.isPresent()) { - // Do not implement the method if it is already implemented by the parent class - processingEnv.getMessager().printMessage(Diagnostic.Kind.OTHER, "Method " + method + " is declared in a parent class.", method); - } else if (fat != FieldAccessorType.UNKNOWN && !printMethodBody(pw, fat, method, hotRodEntityFieldName, fieldType, hotRodVariable.get().asType())) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Could not determine desired semantics of method from its signature", method); - } - } catch (CannotMigrateTypeException ex) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getFormattedMessage(), method); - } - } - }); - - // Implement HotRodDelegate interface - pw.println(" public " + className + " getHotRodEntity() {"); - pw.println(" return this." + ENTITY_VARIABLE + ";"); - pw.println(" }"); - pw.println("}"); - } - } - - private String hotRodEntityField(String fieldName) { - return "this." + ENTITY_VARIABLE + "." + fieldName; - } - - private String getFieldNameForCollectionKey(TypeMirror fieldType, ExecutableElement callingMethod) { - ExecutableElement collectionKey = getCollectionKey(fieldType, callingMethod); - char[] c = determineAttributeFromMethodName(collectionKey).toCharArray(); - c[0] = Character.toLowerCase(c[0]); - return new String(c); - } - - private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType, TypeMirror hotRodFieldType) { - TypeMirror firstParameterType = method.getParameters().isEmpty() - ? types.getNullType() - : method.getParameters().get(0).asType(); - TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); - - switch (accessorType) { - case GETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {"); - pw.println(" return " + hotRodEntityField(fieldName) + " == null ? null : " + migrateToType(method.getReturnType(), hotRodFieldType, hotRodEntityField(fieldName)) + ";"); - pw.println(" }"); - return true; - case SETTER: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - if (! isImmutableFinalType(firstParameterType)) { - pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";"); - } - if (isCollection(firstParameterType)) { - pw.println(" if (p0 != null) {"); - pw.println(" " + removeUndefined(firstParameterType, "p0") + ";"); - pw.println(" if (" + isUndefined("p0") + ") p0 = null;"); - pw.println(" }"); - } - pw.println(" " + hotRodFieldType.toString() + " migrated = p0 == null ? null : " + migrateToType(hotRodFieldType, firstParameterType, "p0") + ";"); - pw.println(" " + hotRodEntityField("updated") + " |= ! Objects.equals(" + hotRodEntityField(fieldName) + ", migrated);"); - pw.println(" " + hotRodEntityField(fieldName) + " = migrated;"); - pw.println(" }"); - return true; - case COLLECTION_ADD: - TypeMirror collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - if (! isImmutableFinalType(firstParameterType)) { - pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";"); - } - if (isCollection(firstParameterType)) { - pw.println(" if (p0 != null) " + removeUndefined(firstParameterType, "p0") + ";"); - } - pw.println(" if (" + isUndefined("p0") + ") return;"); - pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation(typeElement, "") + "; }"); - pw.println(" " + collectionItemType.toString() + " migrated = " + migrateToType(collectionItemType, firstParameterType, "p0") + ";"); - if (isSetType(typeElement)) { - pw.println(" " + hotRodEntityField("updated") + " |= " + hotRodEntityField(fieldName) + ".add(migrated);"); - } else { - pw.println(" " + hotRodEntityField(fieldName) + ".add(migrated);"); - pw.println(" " + hotRodEntityField("updated") + " = true;"); - } - pw.println(" }"); - return true; - case COLLECTION_DELETE: - { - collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0); - boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID; - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - if (isMapType(typeElement)) { - // Maps are stored as sets - pw.println(" boolean removed = " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey(" - + hotRodEntityField(fieldName) + ", " - + "p0, " - + keyGetterReference(collectionItemType) + ");" - ); - pw.println(" " + hotRodEntityField("updated") + " |= removed;"); - } else { - pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { return" + (needsReturn ? " false" : "") + "; }"); - pw.println(" boolean removed = " + hotRodEntityField(fieldName) + ".remove(p0);"); - pw.println(" " + hotRodEntityField("updated") + " |= removed;"); - } - if (needsReturn) pw.println(" return removed;"); - pw.println(" }"); - return true; - } - case COLLECTION_DELETE_BY_ID: - { - boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID; - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {"); - pw.println(" boolean removed = " + hotRodEntityField(fieldName) + " != null && " + hotRodEntityField(fieldName) + ".removeIf(o -> Objects.equals(o." + getFieldNameForCollectionKey(fieldType, method) + ", p0));"); - pw.println(" " + hotRodEntityField("updated") + " |= removed;"); - if (needsReturn) pw.println(" return removed;"); - pw.println(" }"); - return true; - } - case COLLECTION_GET_BY_ID: - { - collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0); - TypeMirror returnTypeGeneric = getGenericsDeclaration(method.getReturnType()).get(0); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {"); - pw.println(" if (" + hotRodEntityField(fieldName) + " == null || " + hotRodEntityField(fieldName) + ".isEmpty()) return Optional.empty();"); - pw.println(" return " + hotRodEntityField(fieldName) + ".stream().filter(o -> Objects.equals(o." + getFieldNameForCollectionKey(fieldType, method) + ", p0)).findFirst().map(e -> " + migrateToType(returnTypeGeneric, collectionItemType, "e") + ");"); - pw.println(" }"); - return true; - } - case MAP_ADD: - collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0); - TypeMirror secondParameterType = method.getParameters().get(1).asType(); - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {"); - if (! isImmutableFinalType(secondParameterType)) { - pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";"); - } - if (isCollection(secondParameterType)) { - pw.println(" if (p1 != null) " + removeUndefined(secondParameterType, "p1") + ";"); - } - pw.println(" boolean valueUndefined = " + isUndefined("p1") + ";"); - pw.println(" if (" + hotRodEntityField(fieldName) + " == null && !valueUndefined) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation((TypeElement) types.asElement(types.erasure(hotRodFieldType)), "") + "; }"); - pw.println(" " + hotRodEntityField("updated") + " |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey(" - + hotRodEntityField(fieldName) + ", " - + "p0, " - + keyGetterReference(collectionItemType) + ");" - ); - pw.println(" " + hotRodEntityField("updated") + " |= !valueUndefined && " + hotRodEntityField(fieldName) - + ".add(" + migrateToType(collectionItemType, new TypeMirror[]{firstParameterType, secondParameterType}, new String[]{"p0", "p1"}) + ");"); - pw.println(" }"); - return true; - case MAP_GET: - pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {"); - collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0); - pw.println(" return " + hotRodUtils.getQualifiedName().toString() + ".getMapValueFromSet(" - + hotRodEntityField(fieldName) + ", " - + "p0, " - + keyGetterReference(collectionItemType) + ", " - + valueGetterReference(collectionItemType) + ");" - ); - pw.println(" }"); - return true; - } - - return false; - } - - private String migrateToType(TypeMirror toType, TypeMirror fromType, String fieldName) { - return migrateToType(toType, new TypeMirror[] {fromType}, new String[]{fieldName}); - } - - private String toSimpleName(TypeMirror typeMirror) { - TypeElement e = elements.getTypeElement(types.erasure(typeMirror).toString()); - return e.getSimpleName().toString(); - } - - private boolean hasField(TypeElement type, String fieldName) { - Optional hotRodVariable = elements.getAllMembers(type).stream() - .filter(VariableElement.class::isInstance) - .map(VariableElement.class::cast) - .filter(variableElement -> variableElement.getSimpleName().toString().equals(fieldName)) - .findFirst(); - - return hotRodVariable.isPresent(); - } - - private String keyGetterReference(TypeMirror type) { - TypeElement typeElement = elements.getTypeElement(types.erasure(type).toString()); - - if (hasField(typeElement, "id")) { - return "e -> e.id"; - } - return hotRodUtils.getQualifiedName().toString() + "::getKey"; - } - - private String valueGetterReference(TypeMirror type) { - if (types.isAssignable(type, abstractHotRodEntity.asType())) { - return toSimpleName(type) + "Delegate::new"; - } - return hotRodUtils.getQualifiedName().toString() + "::getValue"; - } - - private boolean isAssignable(TypeMirror fromType, TypeMirror toType) { - return types.isAssignable(types.erasure(fromType), types.erasure(toType)); - } - - private String migrateToType(TypeMirror toType, TypeMirror[] fromType, String[] fieldNames) { - // No migration needed, fromType is assignable to toType directly - if (fromType.length == 1 && isAssignable(fromType[0], toType) && !isCollection(fromType[0])) { - return fieldNames[0]; - } - - // Solve migration of data within collections - if (fromType.length == 1) { - if (isAssignable(fromType[0], toType)) { // First case, the collection is the same - TypeMirror fromGeneric = getGenericsDeclaration(fromType[0]).get(0); - TypeMirror toGeneric = getGenericsDeclaration(toType).get(0); - - // Generics are assignable too, so we can just assign the same value - if (isAssignable(fromGeneric, toGeneric)) return fieldNames[0]; - - return hotRodUtils.getQualifiedName().toString() + ".migrate" + toSimpleName(fromType[0]) + "(" - + fieldNames[0] + ", " - + "collectionItem -> " + migrateToType(toGeneric, fromGeneric, "collectionItem") + ")"; - } else if (isSetType((TypeElement) types.asElement(types.erasure(toType))) - && isMapType((TypeElement) types.asElement(types.erasure(fromType[0])))) { - TypeMirror setType = getGenericsDeclaration(toType).get(0); - - return hotRodUtils.getQualifiedName().toString() + ".migrateMapToSet(" - + fieldNames[0] + ", " - + hotRodUtils.getQualifiedName().toString() + "::create" + toSimpleName(setType) + "FromMapEntry)"; - } else if (isMapType((TypeElement) types.asElement(types.erasure(toType))) - && isSetType((TypeElement) types.asElement(types.erasure(fromType[0])))) { - TypeMirror setType = getGenericsDeclaration(fromType[0]).get(0); - - return hotRodUtils.getQualifiedName().toString() + ".migrateSetToMap(" - + fieldNames[0] + ", " - + keyGetterReference(setType) + ", " - + valueGetterReference(setType) - + ")"; - } - - } - - if (isAssignable(fromType[0], enumWithStableId.asType())) { - return fieldNames[0] + ".getStableIndex()"; - } - - if (isAssignable(toType, enumWithStableId.asType())) { - return toType.toString() + ".valueOfInteger(" + fieldNames[0] + ")"; - } - - // Try to find constructor that can do the migration - if (findSuitableConstructor(toType, fromType).isPresent()) { - return "new " + toType.toString() + "(" + String.join(", ", fieldNames) + ")"; - } - - if (isAssignable(toType, abstractHotRodEntity.asType())) { - // Check if any of parameters is another Map*Entity - OptionalInt anotherMapEntityIndex = IntStream.range(0, fromType.length) - .filter(i -> isAssignable(fromType[i], abstractEntity.asType())) - .findFirst(); - - // If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method - return "((" + generalHotRodDelegate.getQualifiedName().toString() + "<" + toType.toString() + ">) " + fieldNames[anotherMapEntityIndex.orElse(0)] + ").getHotRodEntity()"; - } - - // Check if any of parameters is another HotRod*Entity - OptionalInt anotherHotRodEntityIndex = IntStream.range(0, fromType.length) - .filter(i -> isAssignable(fromType[i], abstractHotRodEntity.asType())) - .findFirst(); - - if (anotherHotRodEntityIndex.isPresent()) { - // If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method - return "new " + fromType[anotherHotRodEntityIndex.getAsInt()] + "Delegate(" + String.join(", ", fieldNames) + ")"; - } - - return hotRodUtils.getQualifiedName().toString() + ".migrate" + Arrays.stream(fromType).map(this::toSimpleName).collect(Collectors.joining("")) + "To" + toSimpleName(toType) + "(" + String.join(", ", fieldNames) + ")"; - } - - private Optional findSuitableConstructor(TypeMirror desiredType, TypeMirror[] parameters) { - // Try to find constructor that can do the migration - TypeElement type = (TypeElement) types.asElement(desiredType); - return elements.getAllMembers(type) - .stream() - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast) - .filter(ee -> ee.getKind() == ElementKind.CONSTRUCTOR) - .filter(ee -> ee.getParameters().size() == parameters.length) - .filter(method -> IntStream.range(0, parameters.length).allMatch(i -> deepCompareTypes(parameters[i], method.getParameters().get(i).asType()))) - .findFirst(); - } - - - private boolean deepCompareTypes(TypeMirror fromType, TypeMirror toType) { - return types.isAssignable(types.erasure(fromType), types.erasure(toType)) - && deepCompareTypes(getGenericsDeclaration(fromType), getGenericsDeclaration(toType)); - } - - private boolean deepCompareTypes(List fromTypes, List toTypes) { - if (fromTypes.size() == 0 && toTypes.size() == 0) return true; - if (fromTypes.size() != toTypes.size()) return false; - - for (int i = 0; i < fromTypes.size(); i++) { - if (!deepCompareTypes(fromTypes.get(i), toTypes.get(i))) return false; - } - return true; - } - } - - private class HotRodEntityDescriptorGenerator implements Generator { - - @Override - public void generate(TypeElement e) throws IOException { - GenerateHotRodEntityImplementation hotRodAnnotation = e.getAnnotation(GenerateHotRodEntityImplementation.class); - if (!hotRodAnnotation.topLevelEntity()) { - return; - } - - if (hotRodAnnotation.modelClass().isEmpty()) { - Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, "HotRod top-level class needs to have model-class defined"); - } - - 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 descriptorClassName = className + "Descriptor"; - String descriptorSimpleClassName = simpleClassName + "Descriptor"; - - JavaFileObject file = processingEnv.getFiler().createSourceFile(descriptorClassName); - try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) { - if (packageName != null) { - pw.println("package " + packageName + ";"); - } - - pw.println("import org.infinispan.protostream.GeneratedSchema;"); - pw.println("import java.util.function.Function;"); - - generatedAnnotation(pw); - pw.println("public class " + descriptorSimpleClassName - + " implements " - + "org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor<" + className + ", " + className + "Delegate" + ">" - + " {"); - - // model class - pw.println(" @Override\n" + - " public Class getModelTypeClass() {\n" + - " return " + hotRodAnnotation.modelClass() + ".class;\n" + - " }"); - - // entity class - pw.println(" @Override\n" + - " public Class<" + className + "> getEntityTypeClass() {\n" + - " return " + className + ".class;\n" + - " }"); - - // cache name - boolean isMethodCall = hotRodAnnotation.cacheName().contains("("); - String quotes = isMethodCall ? "" : "\""; - pw.println(" @Override\n" + - " public String getCacheName() {\n" + - (hotRodAnnotation.cacheName().isEmpty() ? - " return org.keycloak.models.map.storage.ModelEntityUtil.getModelName(" + hotRodAnnotation.modelClass() + ".class);\n" - : " return " + quotes + hotRodAnnotation.cacheName() + quotes + ";\n") + - " }"); - - // delegate provider - pw.println(" @Override\n" + - " public Function<" + className + ", " + className + "Delegate> getHotRodDelegateProvider() {\n" + - " return " + className + "Delegate::new;\n" + - " }"); - - - // Current version - pw.println(" @Override\n" + - " public Integer getCurrentVersion() {\n" + - " return " + className + ".VERSION;\n" + - " }"); - - // Current schema - pw.println(" @Override\n" + - " public GeneratedSchema getProtoSchema() {\n" + - " return " + className + "." + simpleClassName + "Schema.INSTANCE" + ";\n" + - " }"); - - pw.println("}"); - } - - autogenerated.add(" ENTITY_DESCRIPTOR_MAP.put(" + hotRodAnnotation.modelClass() + ".class, new " + descriptorClassName + "());" ); - } - } -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/PrintWriterNoJavaLang.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/PrintWriterNoJavaLang.java deleted file mode 100644 index 99caca4f8cd..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/PrintWriterNoJavaLang.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 java.io.PrintWriter; -import java.io.Writer; - -/** - * - * @author hmlnarik - */ -public class PrintWriterNoJavaLang extends PrintWriter { - - public PrintWriterNoJavaLang(Writer out) { - super(out); - } - - @Override - public void println(String x) { - super.println(x == null ? x : x.replaceAll("java.lang.", "")); - } - -} diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java deleted file mode 100644 index f7444bcf961..00000000000 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.IgnoreForEntityImplementationGenerator; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Collectors; -import javax.lang.model.element.Element; -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.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor8; - -/** - * - * @author hmlnarik - */ -public class Util { - - private static final Set LIST_TYPES = Set.of(List.class.getCanonicalName(), ArrayList.class.getCanonicalName(), LinkedList.class.getCanonicalName()); - private static final Set SET_TYPES = Set.of(Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), HashSet.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName(), Collection.class.getCanonicalName()); - private static final Set MAP_TYPES = Set.of(Map.class.getCanonicalName(), HashMap.class.getCanonicalName()); - - public static List getGenericsDeclaration(TypeMirror fieldType) { - List res = new LinkedList<>(); - - fieldType.accept(new SimpleTypeVisitor8>() { - @Override - public Void visitDeclared(DeclaredType t, List p) { - List typeArguments = t.getTypeArguments(); - res.addAll(typeArguments); - return null; - } - }, res); - - return res; - } - - public static String methodParameters(List parameters) { - return parameters.stream() - .map(p -> p.asType() + " " + p.getSimpleName()) - .collect(Collectors.joining(", ")); - } - - public static boolean isCollectionType(TypeElement typeElement) { - return isListType(typeElement) || isSetType(typeElement); - } - - public static boolean isListType(TypeElement typeElement) { - Name name = typeElement.getQualifiedName(); - return LIST_TYPES.contains(name.toString()); - } - - public static boolean isSetType(TypeElement typeElement) { - Name name = typeElement.getQualifiedName(); - return SET_TYPES.contains(name.toString()); - } - - public static boolean isMapType(TypeElement typeElement) { - Name name = typeElement.getQualifiedName(); - return MAP_TYPES.contains(name.toString()); - } - - public static boolean isNotIgnored(Element el) { - do { - IgnoreForEntityImplementationGenerator a = el.getAnnotation(IgnoreForEntityImplementationGenerator.class); - if (a != null) { - return false; - } - el = el.getEnclosingElement(); - } while (el != null); - return true; - } - - protected static Optional findParentMethodImplementation(List allParentMembers, ExecutableElement method) { - return allParentMembers.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)) - .findAny(); - } - - public static String singularToPlural(String word) { - if (word.endsWith("y")) { - return word.substring(0, word.length() -1) + "ies"; - } else if (word.endsWith("s")) { - return word + "es"; - } else { - return word + "s"; - } - } - - public static String pluralToSingular(String word) { - if (word.endsWith("ies")) { - return word.substring(0, word.length() - 3) + "y"; - } else if (word.endsWith("ses")) { - return word.substring(0, word.length() - 2); - } else { - return word.endsWith("s") ? word.substring(0, word.length() - 1) : word; - } - } -} diff --git a/model/map-file/pom.xml b/model/map-file/pom.xml deleted file mode 100644 index 1fba6e343ab..00000000000 --- a/model/map-file/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - keycloak-model-pom - org.keycloak - 999.0.0-SNAPSHOT - - 4.0.0 - - keycloak-model-map-file - Keycloak Model Map File - - - 11 - 11 - 11 - - - - - org.keycloak - keycloak-model-map - ${project.version} - - - - org.snakeyaml - snakeyaml-engine - - - \ No newline at end of file diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileCriteriaBuilder.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileCriteriaBuilder.java deleted file mode 100644 index f1fa0bfb860..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileCriteriaBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 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.storage.file; - -import org.keycloak.models.map.storage.criteria.DescriptiveModelCriteria; -import org.keycloak.models.map.storage.criteria.ModelCriteriaNode; - -/** - * - * @author hmlnarik - */ -public class FileCriteriaBuilder extends DescriptiveModelCriteria> { - - private static final FileCriteriaBuilder INSTANCE = new FileCriteriaBuilder<>(null); - - private FileCriteriaBuilder(ModelCriteriaNode node) { - super(node); - } - - @SuppressWarnings("unchecked") - public static FileCriteriaBuilder criteria() { - return (FileCriteriaBuilder) INSTANCE; - } - - @Override - protected FileCriteriaBuilder instantiateForNode(ModelCriteriaNode targetNode) { - return new FileCriteriaBuilder<>(targetNode); - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileCrudOperations.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileCrudOperations.java deleted file mode 100644 index 46ea553fe60..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileCrudOperations.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright 2023 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.storage.file; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.StackUtil; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.ExpirationUtils; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.StringKeyConverter.StringKey; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.models.map.storage.file.common.MapEntityContext; -import org.keycloak.models.map.storage.file.yaml.PathWriter; -import org.keycloak.models.map.storage.file.yaml.YamlParser; -import org.keycloak.models.map.storage.file.yaml.YamlWritingMechanism; -import org.keycloak.storage.SearchableModelField; -import org.snakeyaml.engine.v2.emitter.Emitter; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.util.Arrays; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.snakeyaml.engine.v2.api.DumpSettings; -import static org.keycloak.utils.StreamsUtil.paginatedStream; - -public abstract class FileCrudOperations implements CrudOperations, HasRealmId { - - private static final Logger LOG = Logger.getLogger(FileCrudOperations.class); - private String defaultRealmId; - private final Class entityClass; - private final Function dataDirectoryFunc; - private final Function suggestedPath; - private final boolean isExpirableEntity; - private final Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates; - - private static final Map, Map, MapModelCriteriaBuilder.UpdatePredicatesFunc>> ENTITY_FIELD_PREDICATES = new HashMap<>(); - - public static final String SEARCHABLE_FIELD_REALM_ID_FIELD_NAME = ClientModel.SearchableFields.REALM_ID.getName(); - public static final String FILE_SUFFIX = ".yaml"; - public static final DumpSettings DUMP_SETTINGS = DumpSettings.builder() - .setIndent(4) - .setIndicatorIndent(2) - .setIndentWithIndicator(false) - .build(); - - public FileCrudOperations(Class entityClass, - Function dataDirectoryFunc, - Function suggestedPath, - boolean isExpirableEntity) { - this.entityClass = entityClass; - this.dataDirectoryFunc = dataDirectoryFunc; - this.suggestedPath = suggestedPath; - this.isExpirableEntity = isExpirableEntity; - this.fieldPredicates = new IdentityHashMap<>(getPredicates(entityClass)); - this.fieldPredicates.keySet().stream() // Ignore realmId since this is treated in reading differently - .filter(f -> Objects.equals(SEARCHABLE_FIELD_REALM_ID_FIELD_NAME, f.getName())) - .findAny() - .ifPresent(key -> this.fieldPredicates.replace(key, (builder, op, params) -> builder)); - } - - @SuppressWarnings("unchecked") - public static Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> getPredicates(Class entityClass) { - return (Map) ENTITY_FIELD_PREDICATES.computeIfAbsent(entityClass, n -> { - Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates = new IdentityHashMap<>(MapFieldPredicates.getPredicates(ModelEntityUtil.getModelType(entityClass))); - fieldPredicates.keySet().stream() // Ignore realmId since this is treated in reading differently - .filter(f -> Objects.equals(SEARCHABLE_FIELD_REALM_ID_FIELD_NAME, f.getName())) - .findAny() - .ifPresent(key -> fieldPredicates.replace(key, (builder, op, params) -> builder)); - - return (Map) fieldPredicates; - }); - } - - protected Path getPathForEscapedId(String[] escapedIdPathArray) { - Path parentDirectory = getDataDirectory(); - Path targetPath = parentDirectory; - for (String path : escapedIdPathArray) { - targetPath = targetPath.resolve(path).normalize(); - if (!targetPath.getParent().equals(parentDirectory)) { - LOG.warnf("Path traversal detected: %s", Arrays.toString(escapedIdPathArray)); - return null; - } - parentDirectory = targetPath; - } - - return targetPath.resolveSibling(targetPath.getFileName() + FILE_SUFFIX); - } - - protected Path getPathForEscapedId(String escapedId) { - if (escapedId == null) { - throw new IllegalStateException("Invalid ID to escape"); - } - - String[] escapedIdArray = ID_COMPONENT_SEPARATOR_PATTERN.split(escapedId); - return getPathForEscapedId(escapedIdArray); - } - - // Percent sign + Unix (/) and https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file reserved characters - private static final Pattern RESERVED_CHARACTERS = Pattern.compile("[%<:>\"/\\\\|?*=]"); - public static final String ID_COMPONENT_SEPARATOR = ":"; - private static final String ESCAPING_CHARACTER = "="; - private static final Pattern ID_COMPONENT_SEPARATOR_PATTERN = Pattern.compile(Pattern.quote(ID_COMPONENT_SEPARATOR) + "+"); - - private static String[] escapeId(String[] idArray) { - if (idArray == null || idArray.length == 0 || idArray.length == 1 && idArray[0] == null) { - return null; - } - return Stream.of(idArray) - .map(FileCrudOperations::escapeId) - .toArray(String[]::new); - } - - private static String escapeId(String id) { - Objects.requireNonNull(id, "ID must be non-null"); - - StringBuilder idEscaped = new StringBuilder(); - Matcher m = RESERVED_CHARACTERS.matcher(id); - while (m.find()) { - m.appendReplacement(idEscaped, String.format(ESCAPING_CHARACTER + "%02x", (int) m.group().charAt(0))); - } - m.appendTail(idEscaped); - final Path pId = Path.of(idEscaped.toString()); - - return pId.toString(); - } - - public static boolean canParseFile(Path p) { - final String fn = p.getFileName().toString(); - try { - return Files.isRegularFile(p) - && Files.size(p) > 0L - && ! fn.startsWith(".") - && fn.endsWith(FILE_SUFFIX) - && Files.isReadable(p); - } catch (IOException ex) { - return false; - } - } - - protected V parse(Path fileName) { - getLastModifiedTime(fileName); - final V parsedObject = YamlParser.parse(fileName, new MapEntityContext<>(entityClass)); - if (parsedObject == null) { - LOG.debugf("Could not parse %s%s", fileName, StackUtil.getShortStackTrace()); - return null; - } - - final String fileNameStr = fileName.getFileName().toString(); - final String idFromFilename = fileNameStr.substring(0, fileNameStr.length() - FILE_SUFFIX.length()); - String escapedId = determineKeyFromValue(parsedObject, idFromFilename); - if (escapedId == null) { - LOG.tracef("Determined ID from filename: %s%s", idFromFilename); - escapedId = idFromFilename; - } else if (!escapedId.endsWith(idFromFilename)) { - LOG.warnf("Id \"%s\" does not conform with filename \"%s\", expected: %s", escapedId, fileNameStr, escapeId(escapedId)); - } - - parsedObject.setId(escapedId); - parsedObject.clearUpdatedFlag(); - - return parsedObject; - } - - @Override - public V create(V value) { - // TODO: Lock realm directory for changes (e.g. on realm deletion) - String escapedId = value.getId(); - - writeYamlContents(getPathForEscapedId(escapedId), value); - - return value; - } - - public String determineKeyFromValue(V value, String lastIdComponentIfUnset) { - String[] proposedId = suggestedPath.apply(value); - - if (proposedId == null || proposedId.length == 0) { - return lastIdComponentIfUnset; - } else if (proposedId[proposedId.length - 1] == null) { - proposedId[proposedId.length - 1] = lastIdComponentIfUnset; - } - - String[] escapedProposedId = escapeId(proposedId); - final String res = String.join(ID_COMPONENT_SEPARATOR, escapedProposedId); - if (LOG.isTraceEnabled()) { - LOG.tracef("determineKeyFromValue: got %s (%s) for %s", res, res == null ? null : String.join(" [/] ", proposedId), value); - } - return res; - } - - /** - * Returns escaped ID - relative file name in the file system with path separator {@link #ID_COMPONENT_SEPARATOR}. - * - * @param value Object - * @param forCreate Whether this is for create operation ({@code true}) or - * @return - */ - @Override - public String determineKeyFromValue(V value) { - final boolean randomId; - String[] proposedId = suggestedPath.apply(value); - - if (proposedId == null || proposedId.length == 0) { - randomId = value.getId() == null; - proposedId = new String[] { value.getId() == null ? StringKey.INSTANCE.yieldNewUniqueKey() : value.getId() }; - } else if (proposedId[proposedId.length - 1] == null) { - randomId = true; - proposedId[proposedId.length - 1] = StringKey.INSTANCE.yieldNewUniqueKey(); - } else { - randomId = false; - } - - String[] escapedProposedId = escapeId(proposedId); - Path sp = getPathForEscapedId(escapedProposedId); // sp will never be null - - final Path parentDir = sp.getParent(); - if (! Files.isDirectory(parentDir)) { - try { - Files.createDirectories(parentDir); - } catch (IOException ex) { - throw new IllegalStateException("Directory does not exist and cannot be created: " + parentDir, ex); - } - } - - for (int counter = 0; counter < 100; counter++) { - LOG.tracef("Attempting to create file %s", sp, StackUtil.getShortStackTrace()); - try { - final String res = String.join(ID_COMPONENT_SEPARATOR, escapedProposedId); - touch(res, sp); - LOG.tracef("determineKeyFromValue: got %s for created %s", res, value); - return res; - } catch (FileAlreadyExistsException ex) { - if (! randomId) { - throw new ModelDuplicateException("File " + sp + " already exists!"); - } - final String lastComponent = StringKey.INSTANCE.yieldNewUniqueKey(); - escapedProposedId[escapedProposedId.length - 1] = lastComponent; - sp = getPathForEscapedId(escapedProposedId); - } catch (IOException ex) { - throw new IllegalStateException("Could not create file " + sp, ex); - } - } - - return null; - } - - @Override - public V read(String key) { - return Optional.ofNullable(key) - .map(this::getPathForEscapedId) - .filter(Files::isReadable) - .map(this::parse) - .orElse(null); - } - - public MapModelCriteriaBuilder createCriteriaBuilder() { - return new MapModelCriteriaBuilder<>(StringKeyConverter.StringKey.INSTANCE, fieldPredicates); - } - - @Override - public Stream read(QueryParameters queryParameters) { - final List paths; - FileCriteriaBuilder cb = queryParameters.getModelCriteriaBuilder().flashToModelCriteriaBuilder(FileCriteriaBuilder.criteria()); - String realmId = (String) cb.getSingleRestrictionArgument(SEARCHABLE_FIELD_REALM_ID_FIELD_NAME); - setRealmId(realmId); - - final Path dataDirectory = getDataDirectory(); - if (!Files.isDirectory(dataDirectory)) { - return Stream.empty(); - } - - // We cannot use Files.find since it throws an UncheckedIOException if it lists a file which is removed concurrently - // before its BasicAttributes can be retrieved for its BiPredicate parameter - try (Stream dirStream = Files.walk(dataDirectory, entityClass == MapRealmEntity.class ? 1 : 3)) { - // The paths list has to be materialized first, otherwise "dirStream" would be closed - // before the resulting stream would be read and would return empty result - paths = dirStream.collect(Collectors.toList()); - } catch (IOException | UncheckedIOException ex) { - LOG.warnf(ex, "Error listing %s", dataDirectory); - return Stream.empty(); - } - Stream res = paths.stream() - .filter(FileCrudOperations::canParseFile) - .map(this::parse) - .filter(Objects::nonNull); - - MapModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder().flashToModelCriteriaBuilder(createCriteriaBuilder()); - - Predicate keyFilter = mcb.getKeyFilter(); - Predicate entityFilter; - - if (isExpirableEntity) { - entityFilter = mcb.getEntityFilter().and(ExpirationUtils::isNotExpired); - } else { - entityFilter = mcb.getEntityFilter(); - } - - res = res.filter(e -> keyFilter.test(e.getId()) && entityFilter.test(e)); - - if (!queryParameters.getOrderBy().isEmpty()) { - res = res.sorted(MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream())); - } - - return paginatedStream(res, queryParameters.getOffset(), queryParameters.getLimit()); - } - - @Override - public V update(V value) { - String escapedId = value.getId(); - - Path sp = getPathForEscapedId(escapedId); - if (sp == null) { - throw new IllegalArgumentException("Invalid path: " + escapedId); - } - - checkIsSafeToModify(sp); - - // TODO: improve locking - synchronized (FileMapStorageProviderFactory.class) { - writeYamlContents(sp, value); - } - - return value; - } - - @Override - public boolean delete(String key) { - return Optional.ofNullable(key) - .map(this::getPathForEscapedId) - .map(this::removeIfExists) - .orElse(false); - } - - @Override - public long delete(QueryParameters queryParameters) { - return read(queryParameters).map(AbstractEntity::getId).map(this::delete).filter(a -> a).count(); - } - - @Override - public long getCount(QueryParameters queryParameters) { - return read(queryParameters).count(); - } - - @Override - public String getRealmId() { - return defaultRealmId; - } - - @Override - public void setRealmId(String realmId) { - this.defaultRealmId = realmId; - } - - private Path getDataDirectory() { - return dataDirectoryFunc.apply(defaultRealmId == null ? null : escapeId(defaultRealmId)); - } - - private void writeYamlContents(Path sp, V value) { - Path tempSp = sp.resolveSibling("." + getTxId() + "-" + sp.getFileName()); - try (PathWriter w = new PathWriter(tempSp)) { - final Emitter emitter = new Emitter(DUMP_SETTINGS, w); - try (YamlWritingMechanism mech = new YamlWritingMechanism(emitter::emit)) { - new MapEntityContext<>(entityClass).writeValue(value, mech); - } - registerRenameOnCommit(tempSp, sp); - } catch (IOException ex) { - throw new IllegalStateException("Cannot write " + sp, ex); - } - } - - protected abstract void touch(String proposedId, Path sp) throws IOException; - - protected abstract boolean removeIfExists(Path sp); - - protected abstract void registerRenameOnCommit(Path tempSp, Path sp); - - protected abstract String getTxId(); - - /** - * Hook to obtain the last modified time of the file identified by the supplied {@link Path}. - * - * @param path the {@link Path} to the file whose last modified time it to be obtained. - * @return the {@link FileTime} corresponding to the file's last modified time. - */ - protected abstract FileTime getLastModifiedTime(final Path path); - - /** - * Hook to validate that it is safe to modify the file identified by the supplied {@link Path}. The primary - * goal is to identify if other transactions have modified the file after it was read by the current transaction, - * preventing updates to a stale entity. - * - * @param path the {@link Path} to the file that is to be modified. - */ - protected abstract void checkIsSafeToModify(final Path path); -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorage.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorage.java deleted file mode 100644 index 314cb4a1977..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorage.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2023 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.storage.file; - -import org.jboss.logging.Logger; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.StringKeyConverter.StringKey; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.storage.ReadOnlyException; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import static org.keycloak.models.map.storage.file.FileCrudOperations.ID_COMPONENT_SEPARATOR; - -/** - * {@link MapStorage} implementation used with the file map storage. - * - * @author Stefan Guilhen - */ -public class FileMapStorage - extends ConcurrentHashMapStorage> { - - private static final Logger LOG = Logger.getLogger(FileMapStorage.class); - - private final List createdPaths = new LinkedList<>(); - private final List pathsToDelete = new LinkedList<>(); - private final Map renameOnCommit = new LinkedHashMap<>(); - private final Map lastModified = new HashMap<>(); - - private final String txId = StringKey.INSTANCE.yieldNewUniqueKey(); - - public static FileMapStorage newInstance(Class entityClass, - Function dataDirectoryFunc, Function suggestedPath, - boolean isExpirableEntity) { - Crud crud = new Crud<>(entityClass, dataDirectoryFunc, suggestedPath, isExpirableEntity); - FileMapStorage store = new FileMapStorage<>(entityClass, crud); - crud.store = store; - return store; - } - - private FileMapStorage(Class entityClass, Crud crud) { - super( - crud, - StringKeyConverter.StringKey.INSTANCE, - DeepCloner.DUMB_CLONER, - MapFieldPredicates.getPredicates(ModelEntityUtil.getModelType(entityClass)), - ModelEntityUtil.getRealmIdField(entityClass) - ); - } - - @Override - public void rollback() { - // remove all temporary and empty files that were created. - this.renameOnCommit.keySet().forEach(FileMapStorage::silentDelete); - this.createdPaths.forEach(FileMapStorage::silentDelete); - super.rollback(); - } - - @Override - public void commit() { - super.commit(); - // check it is still safe to update/delete before moving the temp files into the actual files or deleting them. - Set allChangedPaths = new HashSet<>(); - allChangedPaths.addAll(this.renameOnCommit.values()); - allChangedPaths.addAll(this.pathsToDelete); - allChangedPaths.forEach(this::checkIsSafeToModify); - try { - this.renameOnCommit.forEach(FileMapStorage::move); - this.pathsToDelete.forEach(FileMapStorage::silentDelete); - // TODO: catch exception thrown by move and try to restore any previously completed moves. - } finally { - // ensure all temp files are removed. - this.renameOnCommit.keySet().forEach(FileMapStorage::silentDelete); - // remove any created files that may have been left empty. - this.createdPaths.forEach(path -> silenteDelete(path, true)); - } - } - - private static void move(Path from, Path to) { - try { - Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException ex) { - throw new UncheckedIOException(ex); - } - } - - private static void silentDelete(Path p) { - silenteDelete(p, false); - } - - private static void silenteDelete(final Path path, final boolean checkEmpty) { - try { - if (Files.exists(path)) { - if (!checkEmpty || Files.size(path) == 0) { - Files.delete(path); - } - } - } catch(IOException e) { - // swallow the exception. - } - } - - public void touch(String proposedId, Path path) throws IOException { - if (Optional.ofNullable(tasks.get(proposedId)).map(MapTaskWithValue::getOperation).orElse(null) == MapOperation.DELETE) { - // If deleted in the current transaction before this operation, then do not touch - return; - } - Files.createFile(path); - createdPaths.add(path); - } - - public boolean removeIfExists(Path path) { - final boolean res = ! pathsToDelete.contains(path) && Files.exists(path); - pathsToDelete.add(path); - return res; - } - - void registerRenameOnCommit(Path from, Path to) { - pathsToDelete.remove(to); - this.renameOnCommit.put(from, to); - } - - /** - * Obtains and stores the last modified time of the file identified by the supplied {@link Path}. This value is used - * to determine if the file was changed by another transaction after it was read by this transaction. - * - * @param path the {@link Path} to the file. - */ - FileTime getLastModifiedTime(final Path path) { - try { - BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); - FileTime lastModifiedTime = attr.lastModifiedTime(); - this.lastModified.put(path, lastModifiedTime); - return lastModifiedTime; - } catch (IOException ex) { - throw new IllegalStateException("Could not read file attributes " + path, ex); - } - } - - /** - * Checks if it is safe to modify the file identified by the supplied {@link Path}. In particular, this method - * verifies if the file was changed (removed, updated) after it was read by this transaction. Being it the case, this - * transaction should refrain from performing further updates as it must assume its data has become stale. - * - * @param path the {@link Path} to the file that will be updated. - * @throws IllegalStateException if the file was altered by another transaction. - */ - void checkIsSafeToModify(final Path path) { - try { - // path wasn't previously loaded - log a message and return. - if (this.lastModified.get(path) == null) { - LOG.debugf("File %s was not previously loaded, skipping validation prior to writing", path); - return; - } - // check if the original file was deleted by another transaction. - if (!Files.exists(path)) { - throw new IllegalStateException("File " + path + " was removed by another transaction"); - } - // check if the original file was modified by another transaction. - BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); - long lastModifiedTime = attr.lastModifiedTime().toMillis(); - if (this.lastModified.get(path).toMillis() < lastModifiedTime) { - throw new IllegalStateException("File " + path + " was changed by another transaction"); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - - @Override - public V registerEntityForChanges(V origEntity) { - final V watchedValue = super.registerEntityForChanges(origEntity); - return DeepCloner.DUMB_CLONER.entityFieldDelegate(watchedValue, new IdProtector(watchedValue)); - } - - private static class Crud extends FileCrudOperations { - - private FileMapStorage store; - - public Crud(Class entityClass, Function dataDirectoryFunc, Function suggestedPath, boolean isExpirableEntity) { - super(entityClass, dataDirectoryFunc, suggestedPath, isExpirableEntity); - } - - @Override - protected void touch(String proposedId, Path sp) throws IOException { - store.touch(proposedId, sp); - } - - @Override - protected void registerRenameOnCommit(Path from, Path to) { - store.registerRenameOnCommit(from, to); - } - - @Override - protected boolean removeIfExists(Path sp) { - return store.removeIfExists(sp); - } - - @Override - protected String getTxId() { - return store.txId; - } - - @Override - protected FileTime getLastModifiedTime(final Path sp) { - return store.getLastModifiedTime(sp); - } - - @Override - protected void checkIsSafeToModify(final Path sp) { - store.checkIsSafeToModify(sp); - } - } - - private class IdProtector extends EntityFieldDelegate.WithEntity { - - public IdProtector(V entity) { - super(entity); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void set(EF field, T value) { - String id = entity.getId(); - super.set(field, value); - checkIdMatches(id, field); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void collectionAdd(EF field, T value) { - String id = entity.getId(); - super.collectionAdd(field, value); - checkIdMatches(id, field); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object collectionRemove(EF field, T value) { - String id = entity.getId(); - final Object res = super.collectionRemove(field, value); - checkIdMatches(id, field); - return res; - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void mapPut(EF field, K key, T value) { - String id = entity.getId(); - super.mapPut(field, key, value); - checkIdMatches(id, field); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapRemove(EF field, K key) { - String id = entity.getId(); - final Object res = super.mapRemove(field, key); - checkIdMatches(id, field); - return res; - } - - private > & org.keycloak.models.map.common.EntityField> void checkIdMatches(String id, EF field) throws ReadOnlyException { - final String idNow = map.determineKeyFromValue(entity, ""); - if (! Objects.equals(id, idNow)) { - if (idNow.endsWith(ID_COMPONENT_SEPARATOR) && id.startsWith(idNow)) { - return; - } - - throw new ReadOnlyException("Cannot change " + field + " as that would change primary key"); - } - } - - @Override - public String toString() { - return super.toString() + " [protected ID]"; - } - } -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorageProvider.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorageProvider.java deleted file mode 100644 index 610daaaef85..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorageProvider.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2023 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.storage.file; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage; - -import java.util.function.Function; - -import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName; -import static org.keycloak.models.map.storage.file.FileMapStorageProviderFactory.UNIQUE_HUMAN_READABLE_NAME_FIELD; - -/** - * File-based {@link MapStorageProvider} implementation. - * - * @author Stefan Guilhen - */ -public class FileMapStorageProvider implements MapStorageProvider { - - private final KeycloakSession session; - private final FileMapStorageProviderFactory factory; - private final int factoryId; - - public FileMapStorageProvider(KeycloakSession session, FileMapStorageProviderFactory factory, int factoryId) { - this.session = session; - this.factory = factory; - this.factoryId = factoryId; - } - - @Override - @SuppressWarnings("unchecked") - public MapStorage getMapStorage(Class modelType, MapStorageProviderFactory.Flag... flags) { - return (MapStorage) SessionAttributesUtils.createMapStorageIfAbsent(session, getClass(), modelType, factoryId, () -> createFileMapStorage(modelType)); - } - - private ConcurrentHashMapStorage> createFileMapStorage(Class modelType) { - String areaName = getModelName(modelType, modelType.getSimpleName()); - final Class et = ModelEntityUtil.getEntityType(modelType); - Function uniqueHumanReadableField = (Function) UNIQUE_HUMAN_READABLE_NAME_FIELD.get(et); - - ConcurrentHashMapStorage mapStorage = FileMapStorage.newInstance(et, - factory.getDataDirectoryFunc(areaName), - ((uniqueHumanReadableField == null) ? v -> v.getId() == null ? null : new String[]{v.getId()} : uniqueHumanReadableField), - ExpirableEntity.class.isAssignableFrom(et)); - session.getTransactionManager().enlist(mapStorage); - return mapStorage; - } - - @Override - public void close() { - } -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorageProviderFactory.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorageProviderFactory.java deleted file mode 100644 index bd5980a188c..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/FileMapStorageProviderFactory.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2023 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.storage.file; - -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -import java.io.File; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.regex.Pattern; -import static java.util.Map.entry; -import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName; -import static org.keycloak.models.map.storage.ModelEntityUtil.getModelNames; - -/** - * A {@link MapStorageProviderFactory} that creates file-based {@link MapStorageProvider}s. - * - * @author Stefan Guilhen - */ -public class FileMapStorageProviderFactory implements AmphibianProviderFactory, - MapStorageProviderFactory, - EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "file"; - private Path rootRealmsDirectory; - private final Map> rootAreaDirectories = new HashMap<>(); // Function: (realmId) -> path - private final int factoryId = SessionAttributesUtils.grabNewFactoryIdentifier(); - - protected static final Map, Function> UNIQUE_HUMAN_READABLE_NAME_FIELD = Map.ofEntries( - entry(MapClientEntity.class, ((Function) v -> new String[] { v.getClientId() })), - entry(MapClientScopeEntity.class, ((Function) v -> new String[] { v.getName() })), - entry(MapGroupEntity.class, ((Function) v -> v.getParentId() == null - ? new String[] { v.getName() } - : new String[] { v.getParentId(), v.getName() })), - entry(MapRealmEntity.class, ((Function) v -> new String[] { v.getName()})), - entry(MapRoleEntity.class, ((Function) (v -> v.getClientId() == null - ? new String[] { v.getName() } - : new String[] { v.getClientId(), v.getName() }))), - entry(MapUserEntity.class, ((Function) v -> new String[] { v.getUsername() })), - - // authz - entry(MapResourceServerEntity.class, ((Function) v -> new String[] { v.getClientId() })), - entry(MapPolicyEntity.class, ((Function) v -> new String[] { v.getResourceServerId(), v.getName() })), - entry(MapPermissionTicketEntity.class,((Function) v -> new String[] { v.getResourceServerId(), null })), - entry(MapResourceEntity.class, ((Function) v -> Objects.equals(v.getResourceServerId(), v.getOwner()) - ? new String[] { v.getResourceServerId(), v.getName() } - : new String[] { v.getResourceServerId(), v.getName(), v.getOwner() })), - entry(MapScopeEntity.class, ((Function) v -> new String[] { v.getResourceServerId(), v.getName() })) - ); - - @Override - public MapStorageProvider create(KeycloakSession session) { - return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, FileMapStorageProvider.class, session1 -> new FileMapStorageProvider(session1, this, factoryId)); - } - - @Override - public String getHelpText() { - return "File Map Storage"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public void init(Config.Scope config) { - final String dir = config.get("dir"); - rootRealmsDirectory = dir == null ? null : Path.of(dir); - getModelNames().stream() - .filter(n -> ! Objects.equals(n, getModelName(RealmModel.class))) - .forEach(n -> rootAreaDirectories.put(n, getRootDir(rootRealmsDirectory, n, config.get("dir." + n)))); - - if (rootAreaDirectories != null) { - rootAreaDirectories.put(getModelName(RealmModel.class), realmId -> realmId == null ? rootRealmsDirectory : rootRealmsDirectory.resolve(realmId) ); - } - } - - private static final Pattern FORBIDDEN_CHARACTERS = Pattern.compile("[\\.\\" + File.separator + "]"); - - private static Function getRootDir(Path rootRealmsDirectory, String areaName, String dirFromConfig) { - if (dirFromConfig != null) { - Path p = Path.of(dirFromConfig); - return realmId -> p; - } else { - if (rootRealmsDirectory == null) { - return p -> { throw new IllegalStateException("Directory for " + areaName + " area not configured."); }; - } - - Path a = areaName.startsWith("authz-") ? Path.of("authz", areaName.substring(6)) : Path.of(areaName); - - return realmId -> { - if (realmId == null || FORBIDDEN_CHARACTERS.matcher(realmId).find()) { - throw new IllegalArgumentException("Realm needed for constructing the path to " + areaName + " but not known or invalid: " + realmId); - } - - final Path path = rootRealmsDirectory - .resolve(realmId) - .resolve(a); - - return path; - }; - } - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - public Function getDataDirectoryFunc(String areaName) { - return rootAreaDirectories.get(areaName); - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/BlockContext.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/BlockContext.java deleted file mode 100644 index 3358dd622ed..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/BlockContext.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2023 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.storage.file.common; - -import org.keycloak.models.map.common.UndefinedValuesUtils; -import org.keycloak.models.map.storage.file.yaml.YamlParser; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import static org.keycloak.models.map.common.CastUtils.cast; - -/** - * A class implementing a {@code BlockContext} interface represents a transformer - * from a primitive value / sequence / mapping representation as declared in YAML - * format into a Java object of type {@code V}, with ability to produce - * the {@link #getResult() resulting instance} of parsing. - * - *

- * This transformer handles only a values of a single node in structured file, i.e. - * single value (a primitive value, sequence or mapping). The root level - * is at the beginning of e.g. YAML or JSON document. - * Every mapping key and every sequence value then represents next level of nesting. - * - * @author hmlnarik - * @param Type of the result - */ -public interface BlockContext { - - /** - * Writes the given value using {@link WritingMechanism}. - * - * @param value - * @param mech - */ - void writeValue(V value, WritingMechanism mech); - - /** - * Called after reading a key of map entry in YAML file and before reading its value. - * The key of the entry is represented as {@code nameOfSubcontext} parameter, and - * provides means to specify a {@code YamlContext} for transforming the mapping value - * into appropriate Java object. - * - * @param nameOfSubcontext Key of the map entry - * - * @return Context used for transforming the value, - * or {@code null} if the default primitive / sequence / mapping context should be used instead. - * - * @see DefaultObjectContext - * @see DefaultListContext - * @see DefaultMapContext - */ - BlockContext getContext(String nameOfSubcontext); - - /** - * Modifies the {@link #getResult() result returned} from within this context by - * providing the read mapping entry {@code name} to given {@code value}. - *

- * Called after reading a map entry (both key and value) from the YAML file is finished. - * The entry is represented as {@code name} parameter (key part of the entry) - * and {@code value} (value part of the entry). - *

- * The method is called in the same order as the mapping items appear in the source YAML mapping. - * - * @param name - * @param value - */ - default void add(String name, Object value) { }; - - /** - * Modifies the {@link #getResult() result returned} from within this context by - * providing the read primitive value or a single sequence item in the {@code value} parameter. - *

- * Called after reading a primitive value or a single sequence item - * from the YAML file is finished. - *

- * If the parsed YAML part was a sequence, this method is called in the same order - * as the sequence items appear in the source YAML sequence. - * - * @param value - */ - default void add(Object value) { }; - - /** - * Returns the result of parsing the given part of YAML file. - * @return - */ - V getResult(); - - Class getScalarType(); - - public static class DefaultObjectContext implements BlockContext { - - private final Class objectType; - private T result; - - public DefaultObjectContext(Class objectType) { - this.objectType = objectType; - } - - public static DefaultObjectContext newDefaultObjectContext() { - return new DefaultObjectContext<>(Object.class); - } - - @Override - public Class getScalarType() { - return objectType; - } - - @Override - public void add(Object value) { - result = (T) value; - } - - @Override - public T getResult() { - return result; - } - - @Override - public void writeValue(Object value, WritingMechanism mech) { - if (value == null || (value.getClass() != String.class && UndefinedValuesUtils.isUndefined(value))) return; - mech.writeObject(value); - } - - @Override - public BlockContext getContext(String nameOfSubcontext) { - return null; - } - } - - public static class DefaultListContext implements BlockContext> { - private final List result = new LinkedList<>(); - - protected final Class itemClass; - - public static DefaultListContext newDefaultListContext() { - return new DefaultListContext<>(Object.class); - } - - public DefaultListContext(Class itemClass) { - this.itemClass = itemClass; - } - - @Override - public Class getScalarType() { - return itemClass; - } - - @Override - public void add(Object value) { - result.add(cast(value, itemClass)); - } - - @Override - public List getResult() { - return result; - } - - @Override - @SuppressWarnings("unchecked") - public void writeValue(Collection value, WritingMechanism mech) { - if (UndefinedValuesUtils.isUndefined(value)) return; - mech.writeSequence(() -> value.forEach(v -> getContextByValue(v).writeValue(v, mech))); - } - - @Override - public BlockContext getContext(String nameOfSubcontext) { - return null; - } - - private BlockContext getContextByValue(Object value) { - BlockContext res = getContext(YamlParser.ARRAY_CONTEXT); - if (res != null) { - return res; - } - if (value instanceof Collection) { - return new DefaultListContext<>(itemClass); - } else if (value instanceof Map) { - return DefaultMapContext.newDefaultMapContext(); - } else { - return new DefaultObjectContext<>(itemClass); - } - } - } - - public static class DefaultMapContext implements BlockContext> { - private final Map result = new LinkedHashMap<>(); - - protected final Class itemClass; - - public static DefaultMapContext newDefaultMapContext() { - return new DefaultMapContext<>(Object.class); - } - - public DefaultMapContext(Class itemClass) { - this.itemClass = itemClass; - } - - @Override - public Class getScalarType() { - return itemClass; - } - - @Override - @SuppressWarnings("unchecked") - public void add(String name, Object value) { - result.put(name, (T) value); - } - - @Override - public Map getResult() { - return result; - } - - @Override - public void writeValue(Map value, WritingMechanism mech) { - if (UndefinedValuesUtils.isUndefined(value)) return; - mech.writeMapping(() -> { - final TreeMap sortedMap = new TreeMap<>(value); - sortedMap.forEach( - (key, val) -> mech.writePair( - key, - () -> getContext(key, val).writeValue(val, mech) - ) - ); - }); - } - - @Override - public BlockContext getContext(String nameOfSubcontext) { - return null; - } - - private BlockContext getContext(String nameOfSubcontext, Object value) { - BlockContext res = getContext(nameOfSubcontext); - if (res != null) { - return res; - } - if (value instanceof Collection) { - return new DefaultListContext<>(itemClass); - } else if (value instanceof Map) { - return DefaultMapContext.newDefaultMapContext(); - } else { - return new DefaultObjectContext<>(itemClass); - } - } - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/BlockContextStack.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/BlockContextStack.java deleted file mode 100644 index 7cc6809d00c..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/BlockContextStack.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2023 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.storage.file.common; - -import java.util.LinkedList; -import java.util.function.Supplier; - -/** - * A special stack suited for tracking the parser of a block language, and maintaining - * contextual information for block nesting position in the YAML file. - *

- * The intention is as follows: - * Initially, it contains a single {@link BlockContext} instance which represents - * the root context of the YAML tree. Every sequence item and mapping value - * in the YAML file leads to pushing a new {@link BlockContext} onto the stack - * which is created by the topmost {@link BlockContext#getContext(java.lang.String)} - * method of the topmost {@link BlockContext}. This context is removed from the stack - * once parsing of the respective sequence item or mapping pair is finished. - * - * @author hmlnarik - */ -public class BlockContextStack extends LinkedList> { - - public BlockContextStack(BlockContext rootElement) { - push(rootElement); - } - - /** - * Pushes the subcontext to the stack. - *

- * The subcontext is created by calling {@link BlockContext#getContext(java.lang.String)} - * method. If this method returns {@code null}, the control reverts to producing - * the subcontext using {@code nullProducer} which must return a valid {@link BlockContext} - * object (it must not return {@code null). - * - * @param name - * @param nullProducer - * @return - */ - public BlockContext push(String name, Supplier> nullProducer) { - BlockContext context = peek().getContext(name); - if (context == null) { - context = nullProducer.get(); - } - push(context); - return context; - } -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/MapEntityContext.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/MapEntityContext.java deleted file mode 100644 index 34d0857e0ac..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/MapEntityContext.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2023 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.storage.file.common; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UndefinedValuesUtils; -import org.keycloak.models.map.role.MapRoleEntityFields; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultListContext; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultMapContext; -import java.util.Collection; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeSet; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import org.jboss.logging.Logger; -import static org.keycloak.models.map.common.CastUtils.cast; - -/** - * {@link BlockContext} which handles any entity accompanied with {@link EntityField} field getters and setters, - * namely {@code Map*Entity} classes. - * @author hmlnarik - */ -public class MapEntityContext implements BlockContext { - - private static final Logger LOG = Logger.getLogger(MapEntityContext.class); - - private final Map> nameToEntityField; - private final Map>> contextCreators; - - protected final Class objectClass; - protected final T result; - private static final Map>> CACHE_FIELD_TO_EF = new IdentityHashMap<>(); - private static final Map>>> CACHE_CLASS_TO_CC = new IdentityHashMap<>(); - private final boolean topContext; - private boolean alreadyReadProperty = false; - - public static final String SCHEMA_VERSION = "schemaVersion"; - - public MapEntityContext(Class clazz) { - this(clazz, true); - } - - @SuppressWarnings("unchecked") - public MapEntityContext(Class clazz, boolean topContext) { - this(clazz, - CACHE_FIELD_TO_EF.computeIfAbsent(clazz, MapEntityContext::fieldsToEntityField), - CACHE_CLASS_TO_CC.computeIfAbsent(clazz, MapEntityContext::fieldsToContextCreators), - topContext - ); - } - - protected MapEntityContext( - Class clazz, - Map> nameToEntityField, - Map>> contextCreators, - boolean topContext) { - this.objectClass = clazz; - this.result = DeepCloner.DUMB_CLONER.newInstance(clazz); - this.nameToEntityField = nameToEntityField; - this.contextCreators = contextCreators; - this.topContext = topContext; - } - - protected static Map>> fieldsToContextCreators(Class type) { - if (! ModelEntityUtil.entityFieldsKnown(type)) { - return Collections.emptyMap(); - } - - return ModelEntityUtil.getEntityFields(type) - .map(ef -> Map.entry(ef, Optional.ofNullable(getDefaultContextCreator(ef)))) - .filter(me -> me.getValue().isPresent()) - .collect(Collectors.toMap(me -> me.getKey().getNameCamelCase(), me -> me.getValue().get())); - } - - private static Supplier> getDefaultContextCreator(EntityField ef) { - final Class collectionElementClass = ef.getCollectionElementClass(); - if (collectionElementClass != Void.class) { - if (ModelEntityUtil.entityFieldsKnown(collectionElementClass)) { - return () -> new MapEntitySequenceYamlContext<>(collectionElementClass); - } - } - - final Class mapValueClass = ef.getMapValueClass(); - if (mapValueClass != Void.class) { - if (ModelEntityUtil.entityFieldsKnown(mapValueClass)) { - return () -> new MapEntityMappingYamlContext<>(mapValueClass); - } else if (ATTRIBUTES_NAME.equals(ef.getName())) { - return StringListMapContext::new; - } - } - - return null; - } - - protected static final String ATTRIBUTES_NAME = MapRoleEntityFields.ATTRIBUTES.getName(); - - public static Map> fieldsToEntityField(Class type) { - return ModelEntityUtil.getEntityFields(type).collect(Collectors.toUnmodifiableMap(EntityField::getNameCamelCase, Function.identity())); - } - - @SuppressWarnings("unchecked") - public static boolean setEntityField(T result, EntityField ef, Object value) { - LOG.tracef("Setting %s::%s field", ef, result.getClass()); - if (ef == null) { - return false; - } - - try { - if (ef.getCollectionElementClass() != Void.class && value instanceof Collection) { - Class collectionElementClass = ef.getCollectionElementClass(); - ((Collection) value).forEach(v -> ef.collectionAdd(result, cast(v, collectionElementClass))); - } else if (ef.getMapKeyClass() != Void.class && value instanceof Map) { - Class mapKeyClass = ef.getMapKeyClass(); - Class mapValueClass = ef.getMapValueClass(); - ((Map) value).forEach((k, v) -> ef.mapPut(result, cast(k, mapKeyClass), cast(v, mapValueClass))); - } else { - final Object origValue = ef.get(result); - if (origValue != null) { - LOG.warnf("Overwriting value of %s field", ef.getNameCamelCase()); - } - ef.set(result, cast(value, ef.getFieldClass())); - } - } catch (Exception ex) { - throw new IllegalArgumentException("Exception thrown while setting " + ef + " field", ex); - } - return true; - } - - @Override - public void add(String name, Object value) { - @SuppressWarnings("unchecked") - EntityField ef = (EntityField) nameToEntityField.get(name); - - if (topContext && name.equals(SCHEMA_VERSION)) { - return; // TODO: Check appropriate schema version and potentially update parsing - } - - if (! setEntityField(result, ef, value)) { - LOG.warnf("Ignoring field %s", name); - } - } - - @Override - public Class getScalarType() { - return this.objectClass; - } - - @Override - public T getResult() { - return this.result; - } - - @Override - public BlockContext getContext(String nameOfSubcontext) { - if (topContext && nameOfSubcontext.equals(SCHEMA_VERSION)) { - if (alreadyReadProperty) { - LOG.warnf("%s must be the first property in the object YAML representation", SCHEMA_VERSION); - } - return null; - } - - alreadyReadProperty = true; - Supplier> cc = contextCreators.get(nameOfSubcontext); - if (cc != null) { - return cc.get(); - } - EntityField ef = nameToEntityField.get(nameOfSubcontext); - if (ef != null) { - if (ef.getCollectionElementClass() != Void.class) { - return contextFor(ef.getCollectionElementClass(), MapEntitySequenceYamlContext::new, DefaultListContext::new); - } else if (ef.getMapValueClass() != Void.class) { - if (ef.getMapValueClass() == List.class || Collection.class.isAssignableFrom(ef.getMapValueClass())) { - return new StringListMapContext(); - } - return contextFor(ef.getMapValueClass(), MapEntityMappingYamlContext::new, DefaultMapContext::new); - } - return contextFor(ef.getFieldClass(), MapEntityContext::new, DefaultObjectContext::new); - } - - LOG.warnf("No special context set for field %s", nameOfSubcontext); - return null; - } - - private static BlockContext contextFor(Class clazz, Function, BlockContext> mapContextCreator, Function, BlockContext> defaultCreator) { - return ModelEntityUtil.entityFieldsKnown(clazz) - ? mapContextCreator.apply(clazz) - : defaultCreator.apply(clazz); - } - - @Override - public void writeValue(T entity, WritingMechanism mech) { - if (UndefinedValuesUtils.isUndefined(entity)) return; - - mech.writeMapping(() -> { - if (topContext) { - mech.writePair(SCHEMA_VERSION, () -> mech.writeObject("1.0.Alpha1")); - } - - TreeSet contextNames = new TreeSet<>(nameToEntityField.keySet()); - contextNames.addAll(contextCreators.keySet()); - - for (String contextName : contextNames) { - @SuppressWarnings("unchecked") - EntityField ef = (EntityField) nameToEntityField.get(contextName); - if (ef == null) { - continue; - } - - if (topContext && (ef.getNameCamelCase().equals("id") || ef.getNameCamelCase().equals("realmId"))) { - continue; - } - - Object fieldVal = ef.get(entity); - if (fieldVal != null) { - BlockContext context = getContext(contextName); - if (context != null) { - mech.writePair(contextName, () -> context.writeValue(fieldVal, mech)); - } - } - } - }); - } - - @Override - public String toString() { - return "MapEntityContext[" + objectClass.getCanonicalName() + ']'; - } - - public static class MapEntitySequenceYamlContext extends DefaultListContext { - - public MapEntitySequenceYamlContext(Class itemClass) { - super(itemClass); - } - - @Override - public BlockContext getContext(String nameOfSubcontext) { - return ModelEntityUtil.entityFieldsKnown(itemClass) - ? new MapEntityContext<>(itemClass, false) - : null; - } - - @Override - public void add(String name, Object value) { - if (value instanceof AbstractEntity) { - ((AbstractEntity) value).setId(name); - add(value); - } else { - throw new IllegalArgumentException("Sequence expected, mapping with " + name + " key found instead."); - } - } - } - - public static class MapEntityMappingYamlContext extends DefaultMapContext { - - public MapEntityMappingYamlContext(Class mapValueClass) { - super(mapValueClass); - } - - @Override - public BlockContext getContext(String nameOfSubcontext) { - return ModelEntityUtil.entityFieldsKnown(itemClass) - ? new MapEntityContext<>(itemClass, false) - : super.getContext(nameOfSubcontext); - } - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/StringListMapContext.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/StringListMapContext.java deleted file mode 100644 index 6788653e1f5..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/StringListMapContext.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2023 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.storage.file.common; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.Map; -import java.util.TreeMap; -import org.keycloak.models.map.common.UndefinedValuesUtils; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultListContext; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultMapContext; -import org.keycloak.models.map.storage.file.yaml.YamlParser; -import java.util.List; - -/** - * Block context which suitable for properties stored in a {@code Map>} - * which accepts string mapping key, and entry value is recognized both as a plain value - * (converted to string) or a list of values - * - * @author hmlnarik - */ -public class StringListMapContext extends DefaultMapContext> { - - @SuppressWarnings("unchecked") - public StringListMapContext() { - super((Class) Collection.class); - } - - /** - * Returns a YAML attribute-like context where key of each element - * is stored in YAML file without a given prefix, and in the internal - * representation each key has that prefix. - * - * @param prefix - * @return - */ - public static StringListMapContext prefixed(String prefix) { - return new Prefixed(prefix); - } - - @Override - public AttributeValueYamlContext getContext(String nameOfSubcontext) { - // regardless of the key name, the values need to be converted into List which is the purpose of AttributeValueYamlContext - return new AttributeValueYamlContext(); - } - - @Override - public void writeValue(Map> value, WritingMechanism mech) { - if (UndefinedValuesUtils.isUndefined(value)) return; - mech.writeMapping(() -> { - AttributeValueYamlContext c = getContext(YamlParser.ARRAY_CONTEXT); - for (Map.Entry> entry : new TreeMap<>(value).entrySet()) { - Collection attrValues = entry.getValue(); - mech.writePair(entry.getKey(), () -> c.writeValue(attrValues, mech)); - } - }); - } - - private static class Prefixed extends StringListMapContext { - - protected final String prefix; - - public Prefixed(String prefix) { - this.prefix = prefix; - } - - @Override - public void add(String name, Object value) { - super.add(prefix + name, value); - } - } - - public static class AttributeValueYamlContext extends DefaultListContext { - - public AttributeValueYamlContext() { - super(String.class); - } - - @Override - public void writeValue(Collection value, WritingMechanism mech) { - if (UndefinedValuesUtils.isUndefined(value)) return; - if (value.size() == 1) { - mech.writeObject(value.iterator().next()); - } else { - //sequence - super.writeValue(value, mech); - } - } - - @Override - public void add(Object value) { - if (value != null) { - super.add(String.valueOf(value)); - } - } - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/WritingMechanism.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/WritingMechanism.java deleted file mode 100644 index feb132b9590..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/common/WritingMechanism.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 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.storage.file.common; - -/** - * Class implementing this interface defines mechanism for writing basic structures: primitive types, - * sequences and maps. - */ -public interface WritingMechanism { - - /** - * Writes a value of a primitive type ({@code null}, boolean, number, String). - * @param value - * @return - */ - WritingMechanism writeObject(Object value); - - /** - * Writes a sequence, items of which are written using this mechanism in the {@code task}. - * @param task - * @return - */ - WritingMechanism writeSequence(Runnable task); - - /** - * Writes a mapping, items of which are written using this mechanism in the {@code task}. - * @param task - * @return - */ - WritingMechanism writeMapping(Runnable task); - - /** - * Writes a mapping key/value pair, items of which are written using this mechanism in the {@code task}. - * @param valueTask - * @return - */ - WritingMechanism writePair(String key, Runnable valueTask); - - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/PathWriter.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/PathWriter.java deleted file mode 100644 index 561bfc1b41a..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/PathWriter.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2023 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.storage.file.yaml; - -import java.io.BufferedWriter; -import java.io.Closeable; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.snakeyaml.engine.v2.api.StreamDataWriter; - -/** - * - * @author hmlnarik - */ -public class PathWriter implements StreamDataWriter, Closeable { - - private final BufferedWriter writer; - - public PathWriter(Path path) throws IOException { - this.writer = Files.newBufferedWriter(path); - } - - @Override - public void write(String str) { - try { - this.writer.write(str); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void write(String str, int off, int len) { - try { - this.writer.write(str, off, len); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void flush() { - try { - this.writer.flush(); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void close() throws IOException { - writer.close(); - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/RunOnlyOnce.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/RunOnlyOnce.java deleted file mode 100644 index 2c3bd5ef840..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/RunOnlyOnce.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2023 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.storage.file.yaml; - -import java.util.LinkedList; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * - * @author hmlnarik - */ -class RunOnlyOnce implements Runnable { - - private final AtomicBoolean ran = new AtomicBoolean(false); - private final Runnable preTask; - private final Runnable postTask; - - public RunOnlyOnce(Runnable preTask, Runnable postTask) { - this.preTask = preTask; - this.postTask = postTask; - } - - @Override - public void run() { - if (ran.compareAndSet(false, true) && preTask != null) { - preTask.run(); - } - } - - public void runPostTask() { - if (hasRun() && postTask != null) { - postTask.run(); - } - } - - public boolean hasRun() { - return ran.get(); - } - - @Override - public String toString() { - return "RunOnlyOnce" - + (hasRun() ? " - ran already" : "") - + " " + preTask; - } - - static class List extends LinkedList { - - @Override - public RunOnlyOnce removeLast() { - final RunOnlyOnce res = super.removeLast(); - res.runPostTask(); - return res; - } - } -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/YamlParser.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/YamlParser.java deleted file mode 100644 index 6c467ced22b..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/YamlParser.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright 2023 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.storage.file.yaml; - -import org.keycloak.models.map.common.CastUtils; -import org.keycloak.models.map.storage.file.common.BlockContextStack; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultListContext; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultMapContext; -import org.keycloak.models.map.storage.file.common.BlockContext.DefaultObjectContext; -import java.io.InputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.EnumMap; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; -import org.jboss.logging.Logger; -import org.snakeyaml.engine.v2.api.LoadSettings; -import org.snakeyaml.engine.v2.api.YamlUnicodeReader; -import org.snakeyaml.engine.v2.constructor.StandardConstructor; -import org.snakeyaml.engine.v2.events.Event; -import org.snakeyaml.engine.v2.events.Event.ID; -import org.snakeyaml.engine.v2.events.NodeEvent; -import org.snakeyaml.engine.v2.events.ScalarEvent; -import org.snakeyaml.engine.v2.exceptions.ConstructorException; -import org.snakeyaml.engine.v2.nodes.ScalarNode; -import org.snakeyaml.engine.v2.nodes.Tag; -import org.snakeyaml.engine.v2.parser.Parser; -import org.snakeyaml.engine.v2.parser.ParserImpl; -import org.snakeyaml.engine.v2.resolver.JsonScalarResolver; -import org.snakeyaml.engine.v2.resolver.ScalarResolver; -import org.snakeyaml.engine.v2.scanner.StreamReader; -import org.keycloak.models.map.storage.file.common.BlockContext; - -import java.util.Map; -import org.snakeyaml.engine.v2.constructor.ConstructScalar; -import org.snakeyaml.engine.v2.nodes.Node; -import static org.keycloak.common.util.StackUtil.getShortStackTrace; - -/** - * - * @author hmlnarik - */ -public class YamlParser { - - private static final Logger LOG = Logger.getLogger(YamlParser.class); - public static final String ARRAY_CONTEXT = "$@[]@$"; - - private static final ScalarResolver RESOLVER = new JsonScalarResolver(); - private final Parser parser; - private final BlockContextStack contextStack; - - // Leverage SnakeYaml's translation of primitive values - private static final class MiniConstructor extends StandardConstructor { - - public MiniConstructor() { - super(SETTINGS); - } - - // This has been based on SnakeYaml's own org.snakeyaml.engine.v2.constructor.BaseConstructor.constructObjectNoCheck(Node node) - @SuppressWarnings(value = "unchecked") - public Object constructStandardJavaInstance(ScalarNode node) { - return findConstructorFor(node) - .map(constructor -> constructor.construct(node)) - .orElseThrow(() -> new ConstructorException(null, Optional.empty(), "Could not determine a constructor for the tag " + node.getTag(), node.getStartMark())); - } - - public static final MiniConstructor INSTANCE = new MiniConstructor(); - } - - private static final class NullConstructor extends ConstructScalar { - - @Override - public Object construct(Node node) { - return null; - } - } - - private static final LoadSettings SETTINGS = LoadSettings.builder() - .setAllowRecursiveKeys(false) - .setParseComments(false) - .setTagConstructors(Map.of(Tag.NULL, new NullConstructor())) - .build(); - - public static E parse(Path path, BlockContext initialContext) { - LOG.tracef("parse(%s,%s)%s", path, initialContext, getShortStackTrace()); - - Objects.requireNonNull(path, "Path invalid"); - try (InputStream is = Files.newInputStream(path)) { - if (Files.size(path) == 0) { - return null; - } - Parser p = new ParserImpl(SETTINGS, new StreamReader(SETTINGS, new YamlUnicodeReader(is))); - return new YamlParser<>(p, initialContext).parse(); - } catch (IOException ex) { - LOG.warn(ex); - return null; - } - } - - protected YamlParser(Parser p, BlockContext initialContext) { - this.parser = p; - this.contextStack = new BlockContextStack(initialContext); - } - - @SuppressWarnings("unchecked") - protected E parse() { - consumeEvent(Event.ID.StreamStart, "Expected a stream"); - - if (!parser.checkEvent(Event.ID.StreamEnd)) { - consumeEvent(Event.ID.DocumentStart, "Expected a document in the stream"); - parseNode(); - consumeEvent(Event.ID.DocumentEnd, "Expected a single document in the stream"); - } - - consumeEvent(Event.ID.StreamEnd, "Expected a single document in the stream"); - - return (E) contextStack.pop().getResult(); - } - - protected Object parseNode() { - if (parser.checkEvent(Event.ID.Alias)) { - throw new IllegalStateException("Aliases are not handled at this moment"); - } - Event ev = parser.next(); - if (!(ev instanceof NodeEvent)) { - throw new IllegalArgumentException("Invalid event " + ev); - } -// if (anchor != null) { -// node.setAnchor(anchor); -// anchors.put(anchor, node); -// } -// try { - switch (ev.getEventId()) { - case Scalar: - return parseScalar((ScalarEvent) ev); - case SequenceStart: - return parseSequence(); - case MappingStart: - return parseMapping(); - default: - throw new IllegalStateException("Event not expected " + ev); - } -// } finally { -// anchors.remove(anchor); -// } - } - - /** - * Parses a sequence node inside the current context. Each sequence item is parsed in the context - * supplied by the current - * @return - */ - protected Object parseSequence() { - LOG.tracef("Parsing sequence"); - BlockContext context = contextStack.peek(); - while (! parser.checkEvent(Event.ID.SequenceEnd)) { - context.add(parseNodeInFreshContext(ARRAY_CONTEXT)); - } - consumeEvent(Event.ID.SequenceEnd, "Expected end of sequence"); - return context.getResult(); - } - - /** - * Parses a mapping node inside the current context. Each mapping value is parsed in the context - * supplied by the current context for the mapping key. - * @return - */ - protected Object parseMapping() { - LOG.tracef("Parsing mapping"); - BlockContext context = contextStack.peek(); - while (! parser.checkEvent(Event.ID.MappingEnd)) { - Object key = parseNodeInFreshContext(); - LOG.tracef("Parsed mapping key: %s", key); - if (! (key instanceof String)) { - try { - key = CastUtils.cast(key, String.class); - } catch (IllegalStateException ex) { - throw new IllegalStateException("Invalid key in map: " + key); - } - } - Object value = parseNodeInFreshContext((String) key); - LOG.tracef("Parsed mapping value: %s", value); - context.add((String) key, value); - } - consumeEvent(Event.ID.MappingEnd, "Expected end of mapping"); - return context.getResult(); - } - - /** - * Parses a scalar node inside the current context. - * @return - */ - protected Object parseScalar(ScalarEvent se) { - BlockContext context = contextStack.peek(); - - boolean implicit = se.getImplicit().canOmitTagInPlainScalar(); - final Tag nodeTag; - Class ot = context.getScalarType(); - nodeTag = constructTag(se.getTag(), se.getValue(), implicit, ot); - - ScalarNode node = new ScalarNode(nodeTag, true, se.getValue(), se.getScalarStyle(), se.getStartMark(), se.getEndMark()); - final Object value = MiniConstructor.INSTANCE.constructStandardJavaInstance(node); - context.add(value); - return context.getResult(); - } - - private static final EnumMap>> CONTEXT_CONSTRUCTORS = new EnumMap<>(Event.ID.class); - static { - CONTEXT_CONSTRUCTORS.put(ID.Scalar, DefaultObjectContext::newDefaultObjectContext); - CONTEXT_CONSTRUCTORS.put(ID.SequenceStart, DefaultListContext::newDefaultListContext); - CONTEXT_CONSTRUCTORS.put(ID.MappingStart, DefaultMapContext::newDefaultMapContext); - } - - /** - * Ensure that the next event is the expectedEventId, otherwise throw an exception, and consume that event - */ - private Event consumeEvent(ID expectedEventId, String message) throws IllegalArgumentException { - if (! parser.checkEvent(expectedEventId)) { - Event event = parser.next(); - throw new IllegalArgumentException(message + " at " + event.getStartMark()); - } - return parser.next(); - } - - private static Tag constructTag(Optional tag, String value, boolean implicit) { - // based on org.snakeyaml.engine.v2.composer.Composer.composeScalarNode(Optional anchor, List blockComments) - return tag.filter(t -> ! "!".equals(t)) - .map(Tag::new) - .orElseGet(() -> RESOLVER.resolve(value, implicit)); - } - - private Tag constructTag(Optional tag, String value, boolean implicit, Class ot) { - if (ot == String.class) { - return Tag.STR; - } else { - return constructTag(tag, value, implicit); - } - } - - /** - * Parses the node in a context created for the given {@code key}. - * @param key - * @return - * @throws IllegalStateException - */ - private Object parseNodeInFreshContext(String key) throws IllegalStateException { - Supplier> cc = CONTEXT_CONSTRUCTORS.get(parser.peekEvent().getEventId()); - if (cc == null) { - throw new IllegalStateException("Invalid value in map with key " + key); - } - contextStack.push(key, cc); - Object value = parseNode(); - contextStack.pop(); - return value; - } - - /** - * Parses the node in a fresh context {@link DefaultObjectContext}. - * @return - * @throws IllegalStateException - */ - private Object parseNodeInFreshContext() throws IllegalStateException { - contextStack.push(DefaultObjectContext.newDefaultObjectContext()); - Object value = parseNode(); - contextStack.pop(); - return value; - } - -} diff --git a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/YamlWritingMechanism.java b/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/YamlWritingMechanism.java deleted file mode 100644 index 66a1b1d648c..00000000000 --- a/model/map-file/src/main/java/org/keycloak/models/map/storage/file/yaml/YamlWritingMechanism.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2023 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.storage.file.yaml; - -import org.keycloak.models.map.storage.file.common.WritingMechanism; -import java.io.Closeable; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collections; -import java.util.LinkedList; -import java.util.Optional; -import java.util.function.Consumer; -import org.snakeyaml.engine.v2.common.FlowStyle; -import org.snakeyaml.engine.v2.common.ScalarStyle; -import org.snakeyaml.engine.v2.events.DocumentEndEvent; -import org.snakeyaml.engine.v2.events.DocumentStartEvent; -import org.snakeyaml.engine.v2.events.Event; -import org.snakeyaml.engine.v2.events.ImplicitTuple; -import org.snakeyaml.engine.v2.events.MappingEndEvent; -import org.snakeyaml.engine.v2.events.MappingStartEvent; -import org.snakeyaml.engine.v2.events.ScalarEvent; -import org.snakeyaml.engine.v2.events.SequenceEndEvent; -import org.snakeyaml.engine.v2.events.SequenceStartEvent; -import org.snakeyaml.engine.v2.events.StreamEndEvent; -import org.snakeyaml.engine.v2.events.StreamStartEvent; -import org.snakeyaml.engine.v2.nodes.Tag; - -/** - * Mechanism which produces {@link Event}s for SnakeYaml v2 {@code Emitter}. - * - * @author vramik - */ -public class YamlWritingMechanism implements WritingMechanism, Closeable { - - private final ImplicitTuple implicitTuple = new ImplicitTuple(true, true); - private final Consumer consumer; - private boolean runningPreTasks = false; - private final LinkedList preTasks = new RunOnlyOnce.List(); - - public YamlWritingMechanism(Consumer consumer) { - this.consumer = consumer; - this.preTasks.add(new RunOnlyOnce(this::startDocument, this::endDocument)); - } - - @Override - public void close() { - endDocument(); - } - - @Override - public YamlWritingMechanism writeMapping(Runnable task) { - return writeObject(task, this::startMapping, this::endMapping); - } - - @Override - public YamlWritingMechanism writeSequence(Runnable task) { - return writeObject(task, this::startSequence, this::endSequence); - } - - @Override - public YamlWritingMechanism writePair(String key, Runnable task) { - return writeObject(task, () -> writeObject(key), null); - } - - @Override - public YamlWritingMechanism writeObject(Object value) { - if (! runningPreTasks) { - runningPreTasks = true; - preTasks.forEach(RunOnlyOnce::run); - runningPreTasks = false; - } - this.consumer.accept(new ScalarEvent(Optional.empty(), determineTag(value), implicitTuple, value == null ? "null" : value.toString(), determineStyle(value))); - return this; - } - - private void startDocument() { - this.consumer.accept(new StreamStartEvent()); - this.consumer.accept(new DocumentStartEvent(false, Optional.empty(), Collections.emptyMap())); - } - - private void endDocument() { - this.consumer.accept(new DocumentEndEvent(false)); - this.consumer.accept(new StreamEndEvent()); - } - - private YamlWritingMechanism writeObject(Runnable taskWithOptionalWrite, Runnable preWriteTask, Runnable postWriteTask) { - RunOnlyOnce roo = new RunOnlyOnce(preWriteTask, postWriteTask); - try { - preTasks.addLast(roo); - taskWithOptionalWrite.run(); - } finally { - preTasks.removeLast(); - } - return this; - } - - private void startSequence() { - this.consumer.accept(new SequenceStartEvent(Optional.empty(), Optional.of(Tag.SEQ.getValue()), true, FlowStyle.BLOCK)); - } - - private void endSequence() { - this.consumer.accept(new SequenceEndEvent()); - } - - private void startMapping() { - this.consumer.accept(new MappingStartEvent(Optional.empty(), Optional.of(Tag.MAP.getValue()), true, FlowStyle.BLOCK)); - } - - private void endMapping() { - this.consumer.accept(new MappingEndEvent()); - } - - private Optional determineTag(Object value) { - if (value instanceof String) { - return Optional.of(Tag.STR.getValue()); - } else if (value instanceof Boolean) { - return Optional.of(Tag.BOOL.getValue()); - } else if (value instanceof Integer || value instanceof Long || value instanceof BigInteger) { - return Optional.of(Tag.INT.getValue()); - } else if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) { - return Optional.of(Tag.FLOAT.getValue()); - } else if (value == null) { - return Optional.of(Tag.NULL.getValue()); - } else { - return Optional.empty(); - } - } - - private ScalarStyle determineStyle(Object value) { - if (value instanceof String) { - String sValue = (String) value; - // TODO: Check numeric values and quote those as well - if ("null".equals(sValue)) { - return ScalarStyle.DOUBLE_QUOTED; - } - if (sValue.length() > 120 || sValue.lastIndexOf('\n') > 0) { - return ScalarStyle.FOLDED; - } - } - return ScalarStyle.PLAIN; - } - -} diff --git a/model/map-file/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory b/model/map-file/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory deleted file mode 100644 index b7f981f2570..00000000000 --- a/model/map-file/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.file.FileMapStorageProviderFactory diff --git a/model/map-hot-rod/pom.xml b/model/map-hot-rod/pom.xml deleted file mode 100644 index cc425bb1665..00000000000 --- a/model/map-hot-rod/pom.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - keycloak-model-pom - org.keycloak - 999.0.0-SNAPSHOT - - 4.0.0 - - keycloak-model-map-hot-rod - Keycloak Model Hot Rod - - - - 11 - - 3.8.1 - - - - - org.keycloak - keycloak-model-map - ${project.version} - - - org.infinispan - infinispan-api - - - org.infinispan - infinispan-client-hotrod-jakarta - - - org.infinispan - infinispan-query-dsl - - - org.infinispan - infinispan-remote-query-client - - - org.infinispan.protostream - protostream-processor - provided - - - - junit - junit - test - - - org.hamcrest - hamcrest - test - - - org.keycloak - keycloak-model-build-processor - ${project.version} - true - - - jakarta.persistence - jakarta.persistence-api - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${version.compiler.plugin} - - - - org.infinispan.protostream - protostream-processor - ${infinispan.protostream.processor.version} - - - org.keycloak - keycloak-model-build-processor - ${project.version} - - - - org.infinispan.protostream.annotations.impl.processor.AutoProtoSchemaBuilderAnnotationProcessor - org.keycloak.models.map.processor.GenerateHotRodEntityImplementationsProcessor - - - - - - - - - adding-test-dependency-on-map - - - !maven.test.skip - - - - - org.keycloak - keycloak-model-map - ${project.version} - test - test-jar - - - - - - diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodCrudOperations.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodCrudOperations.java deleted file mode 100644 index 79ff05a7b1a..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodCrudOperations.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.infinispan.client.hotrod.MetadataValue; -import org.infinispan.client.hotrod.RemoteCache; -import org.infinispan.client.hotrod.Search; -import org.infinispan.commons.util.CloseableIterator; -import org.infinispan.query.dsl.Query; -import org.infinispan.query.dsl.QueryFactory; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.AbstractKeycloakTransaction; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.utils.LockObjectsForModification; - -import jakarta.persistence.OptimisticLockException; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Spliterators; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery; -import static org.keycloak.utils.StreamsUtil.closing; - -public class HotRodCrudOperations, M> implements CrudOperations { - - private static final Logger LOG = Logger.getLogger(HotRodCrudOperations.class); - - private final KeycloakSession session; - private final RemoteCache remoteCache; - protected final StringKeyConverter keyConverter; - protected final HotRodEntityDescriptor storedEntityDescriptor; - private final Function delegateProducer; - protected final DeepCloner cloner; - protected boolean isExpirableEntity; - private final Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates; - private final Long lockTimeout; - private final RemoteCache locksCache; - private final Map entityVersionCache = new HashMap<>(); - - public HotRodCrudOperations(KeycloakSession session, RemoteCache remoteCache, StringKeyConverter keyConverter, HotRodEntityDescriptor storedEntityDescriptor, DeepCloner cloner, Long lockTimeout) { - this.session = session; - this.remoteCache = remoteCache; - this.keyConverter = keyConverter; - this.storedEntityDescriptor = storedEntityDescriptor; - this.cloner = cloner; - this.delegateProducer = storedEntityDescriptor.getHotRodDelegateProvider(); - this.isExpirableEntity = ExpirableEntity.class.isAssignableFrom(ModelEntityUtil.getEntityType(storedEntityDescriptor.getModelTypeClass())); - this.fieldPredicates = MapFieldPredicates.getPredicates((Class) storedEntityDescriptor.getModelTypeClass()); - this.lockTimeout = lockTimeout; - HotRodConnectionProvider cacheProvider = session.getProvider(HotRodConnectionProvider.class); - this.locksCache = cacheProvider.getRemoteCache(DefaultHotRodConnectionProviderFactory.HOT_ROD_LOCKS_CACHE_NAME); - } - - @Override - public V create(V value) { - K key = keyConverter.fromStringSafe(value.getId()); - if (key == null) { - key = keyConverter.yieldNewUniqueKey(); - value = cloner.from(keyConverter.keyToString(key), value); - } - - if (isExpirableEntity) { - Long lifespan = getLifespan(value); - if (lifespan != null) { - if (lifespan > 0) { - remoteCache.putIfAbsent(key, value.getHotRodEntity(), lifespan, TimeUnit.MILLISECONDS); - } else { - LOG.warnf("Skipped creation of entity %s in storage due to negative/zero lifespan.", key); - } - return value; - } - } - remoteCache.putIfAbsent(key, value.getHotRodEntity()); - - return value; - } - - private String getLockName(String key) { - return storedEntityDescriptor.getModelTypeClass().getName() + "_" + key; - } - - @Override - public V read(String key) { - Objects.requireNonNull(key, "Key must be non-null"); - K k = keyConverter.fromStringSafe(key); - - if (LockObjectsForModification.isEnabled(session, storedEntityDescriptor.getModelTypeClass())) { - String lockName = getLockName(key); - HotRodLocksUtils.repeatPutIfAbsent(locksCache, lockName, Duration.ofMillis(lockTimeout), 50, true); - - session.getTransactionManager().enlistAfterCompletion(new AbstractKeycloakTransaction() { - @Override - protected void commitImpl() { - HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, lockName); - } - - @Override - protected void rollbackImpl() { - HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, lockName); - } - }); - } - - // Obtain value from Infinispan - MetadataValue entityWithMetadata = remoteCache.getWithMetadata(k); - if (entityWithMetadata == null) return null; - - // store entity version - LOG.tracef("Entity %s read in version %s.%s", key, entityWithMetadata.getVersion(), getShortStackTrace()); - entityVersionCache.put(k, entityWithMetadata.getVersion()); - - // Create delegate that implements Map*Entity - return entityWithMetadata.getValue() != null ? delegateProducer.apply(entityWithMetadata.getValue()) : null; - } - - @Override - public V update(V value) { - K key = keyConverter.fromStringSafe(value.getId()); - - if (isExpirableEntity) { - Long lifespan = getLifespan(value); - if (lifespan != null) { - if (lifespan > 0) { - if (!remoteCache.replaceWithVersion(key, value.getHotRodEntity(), entityVersionCache.get(key), lifespan, TimeUnit.MILLISECONDS, -1, TimeUnit.MILLISECONDS)) { - throw new OptimisticLockException("Entity " + key + " with version " + entityVersionCache.get(key) + " already changed by a different transaction."); - } - } else { - LOG.warnf("Removing entity %s from storage due to negative/zero lifespan.%s", key, getShortStackTrace()); - if (!remoteCache.removeWithVersion(key, entityVersionCache.get(key))) { - throw new OptimisticLockException("Entity " + key + " with version " + entityVersionCache.get(key) + " already changed by a different transaction."); - } - } - - return delegateProducer.apply(value.getHotRodEntity()); - } - } - if (!remoteCache.replaceWithVersion(key, value.getHotRodEntity(), entityVersionCache.get(key))) { - throw new OptimisticLockException("Entity " + key + " with version " + entityVersionCache.get(key) + " already changed by a different transaction."); - } - return delegateProducer.apply(value.getHotRodEntity()); - } - - @Override - public boolean delete(String key) { - K k = keyConverter.fromStringSafe(key); - - Long entityVersion = entityVersionCache.get(k); - if (entityVersion != null) { - if (!remoteCache.removeWithVersion(k, entityVersion)) { - throw new OptimisticLockException("Entity " + key + " with version " + entityVersion + " already changed by a different transaction."); - } - return true; - } - return remoteCache.remove(k) != null; - } - - private static String toOrderString(QueryParameters.OrderBy orderBy) { - SearchableModelField field = orderBy.getModelField(); - String modelFieldName = IckleQueryMapModelCriteriaBuilder.getFieldName(field); - String orderString = orderBy.getOrder().equals(QueryParameters.Order.ASCENDING) ? "ASC" : "DESC"; - - return modelFieldName + " " + orderString; - } - - @Override - public Stream read(QueryParameters queryParameters) { - DefaultModelCriteria dmc = queryParameters.getModelCriteriaBuilder(); - - // Optimization if the criteria contains only one id - String id = (String) dmc.getSingleRestrictionArgument("id"); - - if (id != null) { - // We have a criteria that contains "id EQ 'some_key'". We can change this to reading only some_key using read method and then apply the rest of criteria. - MapModelCriteriaBuilder mapMcb = dmc.flashToModelCriteriaBuilder(new MapModelCriteriaBuilder<>(keyConverter, fieldPredicates)); - V entity = read(id); - if (entity == null) { - return Stream.empty(); - } - K k = keyConverter.fromString(id); - boolean fulfillsQueryCriteria = mapMcb.getKeyFilter().test(k) && mapMcb.getEntityFilter().test(entity); - if (!fulfillsQueryCriteria) { - // entity does not fulfill whole criteria, we can release lock now - if (LockObjectsForModification.isEnabled(session, storedEntityDescriptor.getModelTypeClass())) { - HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, getLockName(id)); - entityVersionCache.remove(k); - } - return Stream.empty(); - } - - return Stream.of(entity); - } - - // workaround if the query contains us.clientId field, in which case don't read by id => read without optimistic locking. - // See https://issues.redhat.com/browse/ISPN-14537 - if (!dmc.isEmpty() && dmc.partiallyEvaluate((field, op, arg) -> - field == UserSessionModel.SearchableFields.CLIENT_ID || field == UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID - ).toString().contains("__TRUE__")) { - Query query = prepareQueryWithPrefixAndParameters(null, queryParameters); - CloseableIterator iterator = paginateQuery(query, queryParameters.getOffset(), - queryParameters.getLimit()).iterator(); - return closing(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)) - .onClose(iterator::close) - .filter(Objects::nonNull) // see https://github.com/keycloak/keycloak/issues/9271 - .map(this.delegateProducer); - } - - // Criteria does not contain only one id, we need to read ids without locking and then read entities one by one pessimistically or optimistically - Query query = prepareQueryWithPrefixAndParameters("SELECT id ", queryParameters); - CloseableIterator iterator = paginateQuery(query, queryParameters.getOffset(), - queryParameters.getLimit()).iterator(); - - return closing(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)) - .onClose(iterator::close) - // Extract ids from the result - .map(a -> a[0]) - .map(String.class::cast) - // read by id => this will register the entity in an ISPN transaction - .map(this::read) - // Entity can be removed in the meanwhile, we need to check for null - .filter(Objects::nonNull); - } - - private Query prepareQueryWithPrefixAndParameters(String prefix, QueryParameters queryParameters) { - IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder() - .flashToModelCriteriaBuilder(createCriteriaBuilder()); - String queryString = (prefix != null ? prefix : "") + iqmcb.getIckleQuery(); - - if (!queryParameters.getOrderBy().isEmpty()) { - queryString += " ORDER BY " + queryParameters.getOrderBy().stream().map(HotRodCrudOperations::toOrderString) - .collect(Collectors.joining(", ")); - } - LOG.tracef("Preparing Ickle query: '%s'%s", queryString, getShortStackTrace()); - QueryFactory queryFactory = Search.getQueryFactory(remoteCache); - Query query = queryFactory.create(queryString); - - query.setParameters(iqmcb.getParameters()); - return query; - } - - @Override - public long getCount(QueryParameters queryParameters) { - IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder() - .flashToModelCriteriaBuilder(createCriteriaBuilder()); - String queryString = iqmcb.getIckleQuery(); - - LOG.tracef("Executing count Ickle query: %s", queryString); - - QueryFactory queryFactory = Search.getQueryFactory(remoteCache); - - Query query = queryFactory.create(queryString); - query.setParameters(iqmcb.getParameters()); - - return query.execute().hitCount().orElse(0); - } - - @Override - public long delete(QueryParameters queryParameters) { - if (queryParameters.getLimit() != null || queryParameters.getOffset() != null) { - throw new IllegalArgumentException("HotRod storage does not support pagination for delete query"); - } - - Query query = prepareQueryWithPrefixAndParameters("DELETE ", queryParameters); - return query.executeStatement(); - } - - @Override - public boolean exists(String key) { - Objects.requireNonNull(key, "Key must be non-null"); - K k = keyConverter.fromStringSafe(key); - - return remoteCache.containsKey(k); - } - - public IckleQueryMapModelCriteriaBuilder createCriteriaBuilder() { - return new IckleQueryMapModelCriteriaBuilder<>(storedEntityDescriptor.getEntityTypeClass()); - } - - // V must be an instance of ExpirableEntity - // returns null if expiration field is not set - // in certain cases can return 0 or negative number, which needs to be handled carefully when using as ISPN lifespan - private Long getLifespan(V value) { - Long expiration = ((ExpirableEntity) value).getExpiration(); - return expiration != null ? expiration - Time.currentTimeMillis() : null; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java deleted file mode 100644 index 99e8c0aa2af..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.infinispan.client.hotrod.RemoteCache; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.models.map.storage.chm.SingleUseObjectMapStorage; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.storage.hotRod.transaction.AllAreasHotRodStoresWrapper; -import org.keycloak.models.map.storage.hotRod.transaction.HotRodRemoteTransactionWrapper; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionMapStorage; -import org.keycloak.storage.SearchableModelField; - -import java.util.Map; - -import static org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory.CLIENT_SESSION_PREDICATES; -import static org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory.CLONER; - -public class HotRodMapStorageProvider implements MapStorageProvider { - - private final KeycloakSession session; - private final HotRodMapStorageProviderFactory factory; - private final boolean jtaEnabled; - private final long lockTimeout; - private AllAreasHotRodStoresWrapper storesWrapper; - - - public HotRodMapStorageProvider(KeycloakSession session, HotRodMapStorageProviderFactory factory, boolean jtaEnabled, long lockTimeout) { - this.session = session; - this.factory = factory; - this.jtaEnabled = jtaEnabled; - this.lockTimeout = lockTimeout; - } - - @Override - public MapStorage getMapStorage(Class modelType, MapStorageProviderFactory.Flag... flags) { - if (storesWrapper == null) initializeTransactionWrapper(modelType); - - // We need to preload client session store before we load user session store to avoid recursive update of storages map - if (modelType == UserSessionModel.class) getMapStorage(AuthenticatedClientSessionModel.class, flags); - - return (MapStorage) storesWrapper.getOrCreateStoreForModel(modelType, () -> createHotRodMapStorage(session, modelType, flags)); - } - - private void initializeTransactionWrapper(Class modelType) { - storesWrapper = new AllAreasHotRodStoresWrapper(); - - // Enlist the wrapper into prepare phase so the changes in the wrapper are executed before HotRod client provided transaction - session.getTransactionManager().enlistPrepare(storesWrapper); - - // If JTA is enabled, the HotRod client provided transaction is automatically enlisted into JTA and we don't need to do anything here - if (!jtaEnabled) { - // If there is no JTA transaction enabled control HotRod client provided transaction manually using - // HotRodRemoteTransactionWrapper - HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class); - HotRodEntityDescriptor entityDescriptor = factory.getEntityDescriptor(modelType); - RemoteCache remoteCache = connectionProvider.getRemoteCache(entityDescriptor.getCacheName()); - session.getTransactionManager().enlist(new HotRodRemoteTransactionWrapper(remoteCache.getTransactionManager())); - } - } - - private & AbstractEntity, M> ConcurrentHashMapStorage createHotRodMapStorage(KeycloakSession session, Class modelType, MapStorageProviderFactory.Flag... flags) { - HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class); - HotRodEntityDescriptor entityDescriptor = (HotRodEntityDescriptor) factory.getEntityDescriptor(modelType); - Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates = MapFieldPredicates.getPredicates((Class) entityDescriptor.getModelTypeClass()); - StringKeyConverter kc = StringKeyConverter.StringKey.INSTANCE; - - // TODO: This is messy, we should refactor this so we don't need to pass kc, entityDescriptor, CLONER to both MapStorage and CrudOperations - if (modelType == SingleUseObjectValueModel.class) { - return new SingleUseObjectMapStorage(new SingleUseObjectHotRodCrudOperations(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), kc, (HotRodEntityDescriptor) entityDescriptor, CLONER, lockTimeout), kc, CLONER, fieldPredicates); - } if (modelType == AuthenticatedClientSessionModel.class) { - return new ConcurrentHashMapStorage(new HotRodCrudOperations(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), - kc, - entityDescriptor, - CLONER, lockTimeout), kc, CLONER, CLIENT_SESSION_PREDICATES); - } if (modelType == UserSessionModel.class) { - return new HotRodUserSessionMapStorage(new HotRodCrudOperations(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), - kc, - entityDescriptor, - CLONER, lockTimeout), kc, CLONER, fieldPredicates, storesWrapper.getOrCreateStoreForModel(AuthenticatedClientSessionModel.class, () -> createHotRodMapStorage(session, AuthenticatedClientSessionModel.class, flags))); - } else { - return new ConcurrentHashMapStorage(new HotRodCrudOperations<>(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), kc, entityDescriptor, CLONER, lockTimeout), kc, CLONER, fieldPredicates); - } - } - - @Override - public void close() { - - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java deleted file mode 100644 index 2dd2c7141c3..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapComponentEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntityDelegate; -import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntityDelegate; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodPermissionTicketEntityDelegate; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodPolicyEntityDelegate; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceEntityDelegate; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntityDelegate; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntityDelegate; -import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate; -import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate; -import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntityDelegate; -import org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; -import org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntityDelegate; -import org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntityDelegate; -import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntityDelegate; -import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.HotRodRealmEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationExecutionEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationFlowEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticatorConfigEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodClientInitialAccessEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodComponentEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderMapperEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodOTPPolicyEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredentialEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntityDelegate; -import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntityDelegate; -import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntityDelegate; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntityDelegate; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntityDelegate; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntityDelegate; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntity; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntityDelegate; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntityDelegate; -import org.keycloak.models.map.user.MapUserConsentEntity; -import org.keycloak.models.map.user.MapUserCredentialEntity; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.user.MapUserFederatedIdentityEntity; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.provider.ProviderConfigurationBuilder; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.transaction.JtaTransactionManagerLookup; - -import java.util.List; -import java.util.Map; - -public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory, MapStorageProviderFactory, EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "hotrod"; - private final int factoryId = SessionAttributesUtils.grabNewFactoryIdentifier(); - private boolean jtaEnabled; - - protected static final Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> CLIENT_SESSION_PREDICATES = MapFieldPredicates.basePredicates(HotRodAuthenticatedClientSessionEntity.ID); - - private Long lockTimeout; - protected final static DeepCloner CLONER = new DeepCloner.Builder() - .constructor(MapRootAuthenticationSessionEntity.class, HotRodRootAuthenticationSessionEntityDelegate::new) - .constructor(MapAuthenticationSessionEntity.class, HotRodAuthenticationSessionEntityDelegate::new) - - .constructor(MapClientEntity.class, HotRodClientEntityDelegate::new) - .constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new) - - .constructor(MapClientScopeEntity.class, HotRodClientScopeEntityDelegate::new) - - .constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new) - - .constructor(MapRoleEntity.class, HotRodRoleEntityDelegate::new) - - .constructor(MapSingleUseObjectEntity.class, HotRodSingleUseObjectEntityDelegate::new) - - .constructor(MapUserEntity.class, HotRodUserEntityDelegate::new) - .constructor(MapUserCredentialEntity.class, HotRodUserCredentialEntityDelegate::new) - .constructor(MapUserFederatedIdentityEntity.class, HotRodUserFederatedIdentityEntityDelegate::new) - .constructor(MapUserConsentEntity.class, HotRodUserConsentEntityDelegate::new) - - .constructor(MapUserLoginFailureEntity.class, HotRodUserLoginFailureEntityDelegate::new) - - .constructor(MapRealmEntity.class, HotRodRealmEntityDelegate::new) - .constructor(MapAuthenticationExecutionEntity.class, HotRodAuthenticationExecutionEntityDelegate::new) - .constructor(MapAuthenticationFlowEntity.class, HotRodAuthenticationFlowEntityDelegate::new) - .constructor(MapAuthenticatorConfigEntity.class, HotRodAuthenticatorConfigEntityDelegate::new) - .constructor(MapClientInitialAccessEntity.class, HotRodClientInitialAccessEntityDelegate::new) - .constructor(MapComponentEntity.class, HotRodComponentEntityDelegate::new) - .constructor(MapIdentityProviderEntity.class, HotRodIdentityProviderEntityDelegate::new) - .constructor(MapIdentityProviderMapperEntity.class, HotRodIdentityProviderMapperEntityDelegate::new) - .constructor(MapOTPPolicyEntity.class, HotRodOTPPolicyEntityDelegate::new) - .constructor(MapRequiredActionProviderEntity.class, HotRodRequiredActionProviderEntityDelegate::new) - .constructor(MapRequiredCredentialEntity.class, HotRodRequiredCredentialEntityDelegate::new) - .constructor(MapWebAuthnPolicyEntity.class, HotRodWebAuthnPolicyEntityDelegate::new) - - .constructor(MapUserSessionEntity.class, HotRodUserSessionEntityDelegate::new) - .constructor(MapAuthenticatedClientSessionEntity.class, HotRodAuthenticatedClientSessionEntityDelegate::new) - - .constructor(MapResourceServerEntity.class, HotRodResourceServerEntityDelegate::new) - .constructor(MapResourceEntity.class, HotRodResourceEntityDelegate::new) - .constructor(MapScopeEntity.class, HotRodScopeEntityDelegate::new) - .constructor(MapPolicyEntity.class, HotRodPolicyEntityDelegate::new) - .constructor(MapPermissionTicketEntity.class, HotRodPermissionTicketEntityDelegate::new) - - .constructor(MapAuthEventEntity.class, HotRodAuthEventEntityDelegate::new) - .constructor(MapAdminEventEntity.class, HotRodAdminEventEntityDelegate::new) - - .build(); - - @Override - public MapStorageProvider create(KeycloakSession session) { - return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, HotRodMapStorageProvider.class, session1 -> new HotRodMapStorageProvider(session1, this, jtaEnabled, lockTimeout)); - } - - public HotRodEntityDescriptor getEntityDescriptor(Class c) { - return AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.get(c); - } - - @Override - public void init(Config.Scope config) { - this.lockTimeout = config.getLong("lockTimeout", 10000L); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - JtaTransactionManagerLookup jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class); - jtaEnabled = jtaLookup != null && jtaLookup.getTransactionManager() != null; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public String getHelpText() { - return "HotRod map storage"; - } - - @Override - public List getConfigMetadata() { - return ProviderConfigurationBuilder.create() - .property() - .name("lockTimeout") - .type("long") - .defaultValue(10000L) - .helpText("The maximum time to wait in milliseconds when waiting for acquiring a pessimistic read lock. If set to negative there is no timeout configured.") - .add().build(); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java deleted file mode 100644 index be4ea420b38..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.Scope; -import org.keycloak.events.Event; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.storage.SearchableModelField; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.UnaryOperator; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.C; -import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.findAvailableNamedParam; -import static org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE; - -public class IckleQueryMapModelCriteriaBuilder implements ModelCriteriaBuilder> { - - private static final int INITIAL_BUILDER_CAPACITY = 250; - private final Class hotRodEntityClass; - private final StringBuilder whereClauseBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY); - private final Map parameters; - private static final Pattern LIKE_PATTERN_DELIMITER = Pattern.compile("%+"); - private static final Pattern NON_ANALYZED_FIELD_REGEX = Pattern.compile("[%_\\\\]"); - public static final Map, String> INFINISPAN_NAME_OVERRIDES = new HashMap<>(); - public static final Set> LOWERCASE_NORMALIZED_MODEL_FIELDS = new HashSet<>(); - - static { - INFINISPAN_NAME_OVERRIDES.put(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "scopeMappings"); - INFINISPAN_NAME_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, "attributes"); - - INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.PARENT_ID, "parentId"); - INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.ASSIGNED_ROLE, "grantedRoles"); - INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.ATTRIBUTE, "attributes"); - - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE, "usernameLowercase"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.USERNAME, "username"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, "serviceAccountClientLink"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "userConsents.clientId"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE, "userConsents.grantedClientScopesIds"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ASSIGNED_ROLE, "rolesMembership"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ASSIGNED_GROUP, "groupsMembership"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ATTRIBUTE, "attributes"); - INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.IDP_AND_USER, "federatedIdentities"); - - INFINISPAN_NAME_OVERRIDES.put(RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, "clientInitialAccesses"); - INFINISPAN_NAME_OVERRIDES.put(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, "components.providerType"); - - INFINISPAN_NAME_OVERRIDES.put(UserSessionModel.SearchableFields.IS_OFFLINE, "offline"); - INFINISPAN_NAME_OVERRIDES.put(UserSessionModel.SearchableFields.CLIENT_ID, "authenticatedClientSessions.clientId"); - - INFINISPAN_NAME_OVERRIDES.put(Resource.SearchableFields.SCOPE_ID, "scopeIds"); - - INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.RESOURCE_ID, "resourceIds"); - INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.SCOPE_ID, "scopeIds"); - INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.ASSOCIATED_POLICY_ID, "associatedPolicyIds"); - INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.CONFIG, "configs"); - - INFINISPAN_NAME_OVERRIDES.put(Event.SearchableFields.EVENT_TYPE, "type"); - } - - static { - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Policy.SearchableFields.NAME); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Policy.SearchableFields.TYPE); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Resource.SearchableFields.NAME); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Resource.SearchableFields.TYPE); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Scope.SearchableFields.NAME); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(ClientModel.SearchableFields.CLIENT_ID); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(GroupModel.SearchableFields.NAME); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(RoleModel.SearchableFields.NAME); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(RoleModel.SearchableFields.DESCRIPTION); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.EMAIL); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.FIRST_NAME); - LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.LAST_NAME); - } - - public IckleQueryMapModelCriteriaBuilder(Class hotRodEntityClass, StringBuilder whereClauseBuilder, Map parameters) { - this.hotRodEntityClass = hotRodEntityClass; - this.whereClauseBuilder.append(whereClauseBuilder); - this.parameters = parameters; - } - - public IckleQueryMapModelCriteriaBuilder(Class hotRodEntityClass) { - this.hotRodEntityClass = hotRodEntityClass; - this.parameters = new HashMap<>(); - } - - public static String getFieldName(SearchableModelField modelField) { - return INFINISPAN_NAME_OVERRIDES.getOrDefault(modelField, modelField.getName()); - } - - private static boolean notEmpty(StringBuilder builder) { - return builder.length() != 0; - } - - @Override - public IckleQueryMapModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - StringBuilder newBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY); - newBuilder.append("("); - - if (notEmpty(whereClauseBuilder)) { - newBuilder.append(whereClauseBuilder).append(" AND ("); - } - - Map newParameters = new HashMap<>(parameters); - newBuilder.append(IckleQueryWhereClauses.produceWhereClause(modelField, op, value, newParameters)); - - if (notEmpty(whereClauseBuilder)) { - newBuilder.append(")"); - } - - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, newBuilder.append(")"), newParameters); - } - - private StringBuilder joinBuilders(IckleQueryMapModelCriteriaBuilder[] builders, String delimiter) { - return new StringBuilder(INITIAL_BUILDER_CAPACITY).append("(").append(Arrays.stream(builders) - .map(IckleQueryMapModelCriteriaBuilder::getWhereClauseBuilder) - .filter(IckleQueryMapModelCriteriaBuilder::notEmpty) - .collect(Collectors.joining(delimiter))).append(")"); - } - - private Map joinParameters(IckleQueryMapModelCriteriaBuilder[] builders) { - return Arrays.stream(builders) - .map(IckleQueryMapModelCriteriaBuilder::getParameters) - .map(Map::entrySet) - .flatMap(Collection::stream) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - @SuppressWarnings("unchecked") - private IckleQueryMapModelCriteriaBuilder[] resolveNamedQueryConflicts(IckleQueryMapModelCriteriaBuilder[] builders) { - final Set existingKeys = new HashSet<>(); - - return Arrays.stream(builders).map(builder -> { - Map oldParameters = builder.getParameters(); - - if (oldParameters.keySet().stream().noneMatch(existingKeys::contains)) { - existingKeys.addAll(oldParameters.keySet()); - return builder; - } - - String newWhereClause = builder.getWhereClauseBuilder().toString(); - Map newParameters = new HashMap<>(); - for (String key : oldParameters.keySet()) { - if (existingKeys.contains(key)) { - // resolve conflict - String newNamedParameter = findAvailableNamedParam(existingKeys, key + "n"); - newParameters.put(newNamedParameter, oldParameters.get(key)); - newWhereClause = newWhereClause.replace(key, newNamedParameter); - existingKeys.add(newNamedParameter); - } else { - newParameters.put(key, oldParameters.get(key)); - existingKeys.add(key); - } - } - - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, new StringBuilder(newWhereClause), newParameters); - }).toArray(IckleQueryMapModelCriteriaBuilder[]::new); - } - - @Override - public IckleQueryMapModelCriteriaBuilder and(IckleQueryMapModelCriteriaBuilder... builders) { - if (builders.length == 0) { - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass); - } - - builders = resolveNamedQueryConflicts(builders); - - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, joinBuilders(builders, " AND "), - joinParameters(builders)); - } - - @Override - public IckleQueryMapModelCriteriaBuilder or(IckleQueryMapModelCriteriaBuilder... builders) { - if (builders.length == 0) { - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass); - } - - builders = resolveNamedQueryConflicts(builders); - - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, joinBuilders(builders, " OR "), - joinParameters(builders)); - } - - @Override - public IckleQueryMapModelCriteriaBuilder not(IckleQueryMapModelCriteriaBuilder builder) { - StringBuilder newBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY); - StringBuilder originalBuilder = builder.getWhereClauseBuilder(); - - if (originalBuilder.length() != 0) { - newBuilder.append("not").append(originalBuilder); - } - - return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, newBuilder, builder.getParameters()); - } - - private StringBuilder getWhereClauseBuilder() { - return whereClauseBuilder; - } - - public static Object sanitizeNonAnalyzed(Object value) { - if (value instanceof String) { - return sanitizeEachUnitAndReplaceDelimiter((String) value, IckleQueryMapModelCriteriaBuilder::sanitizeSingleUnitNonAnalyzed, "%"); - } - - return value; - } - - private static String sanitizeEachUnitAndReplaceDelimiter(String value, UnaryOperator sanitizeSingleUnit, String replacement) { - return LIKE_PATTERN_DELIMITER.splitAsStream(value) - .map(sanitizeSingleUnit) - .collect(Collectors.joining(replacement)) - + (value.endsWith("%") ? replacement : ""); - } - - private static String sanitizeSingleUnitNonAnalyzed(String value) { - return NON_ANALYZED_FIELD_REGEX.matcher(value).replaceAll("\\\\\\\\" + "$0"); - } - - /** - * - * @return Ickle query that represents this QueryBuilder - */ - public String getIckleQuery() { - return "FROM " + HOT_ROD_ENTITY_PACKAGE + "." + hotRodEntityClass.getSimpleName() + " " + C + ((whereClauseBuilder.length() != 0) ? " WHERE " + whereClauseBuilder : ""); - } - - /** - * Ickle queries are created using named parameters to avoid query injections; this method provides mapping - * between parameter names and corresponding values - * - * @return Mapping from name of the parameter to value - */ - public Map getParameters() { - return parameters; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java deleted file mode 100644 index 096f6d0d5d0..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.keycloak.models.map.storage.ModelCriteriaBuilder; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -/** - * This class provides knowledge on how to build Ickle query where clauses for specified {@link ModelCriteriaBuilder.Operator}. - *

- * For example, - *

- * for operator {@link ModelCriteriaBuilder.Operator#EQ} we concatenate left operand and right operand with equal sign: - * {@code fieldName = :parameterName} - *

- * however, for operator {@link ModelCriteriaBuilder.Operator#EXISTS} we add following: - *

- * {@code fieldName IS NOT NULL AND fieldName IS NOT EMPTY"}. - * - * For right side operands we use named parameters to avoid injection attacks. Mapping between named parameter and - * corresponding value is then saved into {@code Map} that is passed to each {@link ExpressionCombinator}. - */ -public class IckleQueryOperators { - private static final Pattern UNWANTED_CHARACTERS_REGEX = Pattern.compile("[^a-zA-Z\\d]"); - public static final String C = "c"; - private static final Map OPERATOR_TO_STRING = new HashMap<>(); - private static final Map OPERATOR_TO_EXPRESSION_COMBINATORS = new HashMap<>(); - - static { - OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.IN, IckleQueryOperators::in); - OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.EXISTS, IckleQueryOperators::exists); - OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.NOT_EXISTS, IckleQueryOperators::notExists); - OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.ILIKE, IckleQueryOperators::like); - OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.LIKE, IckleQueryOperators::like); - - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.EQ, "="); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.NE, "!="); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.LT, "<"); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.LE, "<="); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.GT, ">"); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.GE, ">="); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.LIKE, "LIKE"); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.ILIKE, "LIKE"); - OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.IN, "IN"); - } - - @FunctionalInterface - private interface ExpressionCombinator { - - /** - * Produces an Ickle query where clause for obtained parameters - * - * @param fieldName left side operand - * @param values right side operands - * @param parameters mapping between named parameters and actual parameter values - * @return resulting string that will be part of resulting - */ - String combine(String fieldName, Object[] values, Map parameters); - } - - private static String exists(String modelFieldName, Object[] values, Map parameters) { - String field = C + "." + modelFieldName; - return field + " IS NOT NULL AND " + field + " IS NOT EMPTY"; - } - - private static String notExists(String modelFieldName, Object[] values, Map parameters) { - String field = C + "." + modelFieldName; - return field + " IS NULL OR " + field + " IS EMPTY"; - } - - private static String like(String modelFieldName, Object[] values, Map parameters) { - String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitizeNonAnalyzed(values[0]); - return singleValueOperator(ModelCriteriaBuilder.Operator.LIKE) - .combine(modelFieldName, new String[] {sanitizedValue}, parameters); - } - - private static String in(String modelFieldName, Object[] values, Map parameters) { - if (values == null || values.length == 0) { - return "false"; - } - - final Collection operands; - if (values.length == 1) { - final Object value0 = values[0]; - if (value0 instanceof Collection) { - operands = (Collection) value0; - } else if (value0 instanceof Stream) { - try (Stream valueS = (Stream) value0) { - operands = (Set) valueS.collect(Collectors.toSet()); - } - } else { - operands = Collections.singleton(value0); - } - } else { - operands = new HashSet<>(Arrays.asList(values)); - } - - return operands.isEmpty() ? "false" : C + "." + modelFieldName + " IN (" + operands.stream() - .map(operand -> { - String namedParam = findAvailableNamedParam(parameters.keySet(), modelFieldName); - parameters.put(namedParam, operand); - return ":" + namedParam; - }) - .collect(Collectors.joining(", ")) + - ")"; - } - - private static String removeForbiddenCharactersFromNamedParameter(String name) { - return UNWANTED_CHARACTERS_REGEX.matcher(name).replaceAll( ""); - } - - /** - * Maps {@code namePrefix} to next available parameter name. For example, if {@code namePrefix == "id"} - * and {@code existingNames} set already contains {@code id0} and {@code id1} it returns {@code id2}. - * Any character that is not an alphanumeric will be stripped, so that it works for prefixes like - * {@code "attributes.name"} as well. - * - * This method is used for computing available names for name query parameters. - * Instead of creating generic named parameters that would be hard to debug and read by humans, it creates readable - * named parameters from the prefix. - * - * @param existingNames set of parameter names that are already used in this Ickle query - * @param namePrefix name of the parameter - * @return next available parameter name - */ - public static String findAvailableNamedParam(Set existingNames, String namePrefix) { - String namePrefixCleared = removeForbiddenCharactersFromNamedParameter(namePrefix); - return IntStream.iterate(0, i -> i + 1) - .boxed() - .map(num -> namePrefixCleared + num) - .filter(name -> !existingNames.contains(name)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Cannot create Parameter name for " + namePrefix)); - } - - private static ExpressionCombinator singleValueOperator(ModelCriteriaBuilder.Operator op) { - return (modelFieldName, values, parameters) -> { - if (values.length != 1) throw new RuntimeException("Invalid arguments, expected (" + modelFieldName + "), got: " + Arrays.toString(values)); - - if (values[0] == null && op.equals(ModelCriteriaBuilder.Operator.EQ)) { - return C + "." + modelFieldName + " IS NULL"; - } - - String namedParameter = findAvailableNamedParam(parameters.keySet(), modelFieldName); - parameters.put(namedParameter, values[0]); - - return C + "." + modelFieldName + " " + IckleQueryOperators.operatorToString(op) + " :" + namedParameter; - }; - } - - private static String operatorToString(ModelCriteriaBuilder.Operator op) { - return OPERATOR_TO_STRING.get(op); - } - - private static ExpressionCombinator operatorToExpressionCombinator(ModelCriteriaBuilder.Operator op) { - return OPERATOR_TO_EXPRESSION_COMBINATORS.getOrDefault(op, singleValueOperator(op)); - } - - /** - * Provides a string containing where clause for given operator, field name and values - * - * @param op operator - * @param modelFieldName field name - * @param values values - * @param parameters mapping between named parameters and their values - * @return where clause - */ - public static String combineExpressions(ModelCriteriaBuilder.Operator op, String modelFieldName, Object[] values, Map parameters) { - return operatorToExpressionCombinator(op).combine(modelFieldName, values, parameters); - } - -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java deleted file mode 100644 index 89bf89450f3..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.keycloak.authorization.model.Policy; -import org.keycloak.events.Event; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.storage.StorageId; -import org.keycloak.util.EnumWithStableIndex; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.LOWERCASE_NORMALIZED_MODEL_FIELDS; -import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.getFieldName; - -/** - * This class provides knowledge on how to build Ickle query where clauses for specified {@link SearchableModelField}. - * - * For example, - *

- * for {@link ClientModel.SearchableFields.CLIENT_ID} we use {@link IckleQueryOperators.ExpressionCombinator} for - * obtained {@link ModelCriteriaBuilder.Operator} and use it with field name corresponding to {@link ClientModel.SearchableFields.CLIENT_ID} - *

- * however, for {@link ClientModel.SearchableFields.ATTRIBUTE} we need to compare attribute name and attribute value - * so we create where clause similar to the following: - * {@code (attributes.name = :attributeName) AND ( attributes.value = :attributeValue )} - * - * - */ -public class IckleQueryWhereClauses { - private static final Map, WhereClauseProducer> WHERE_CLAUSE_PRODUCER_OVERRIDES = new HashMap<>(); - - static { - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(GroupModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.IDP_AND_USER, IckleQueryWhereClauses::whereClauseForUserIdpAlias); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, IckleQueryWhereClauses::whereClauseForConsentClientFederationLink); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE, IckleQueryWhereClauses::whereClauseForUsernameCaseInsensitive); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, IckleQueryWhereClauses::whereClauseForCorrespondingSessionId); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(Policy.SearchableFields.CONFIG, IckleQueryWhereClauses::whereClauseForPolicyConfig); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(Event.SearchableFields.EVENT_TYPE, IckleQueryWhereClauses::whereClauseForEnumWithStableIndex); - WHERE_CLAUSE_PRODUCER_OVERRIDES.put(AdminEvent.SearchableFields.OPERATION_TYPE, IckleQueryWhereClauses::whereClauseForEnumWithStableIndex); - } - - @FunctionalInterface - private interface WhereClauseProducer { - String produceWhereClause(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters); - } - - private static String produceWhereClause(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - return IckleQueryOperators.combineExpressions(op, modelFieldName, values, parameters); - } - - private static WhereClauseProducer whereClauseProducerForModelField(SearchableModelField modelField) { - return WHERE_CLAUSE_PRODUCER_OVERRIDES.getOrDefault(modelField, IckleQueryWhereClauses::produceWhereClause); - } - - /** - * Produces where clause for given {@link SearchableModelField}, {@link ModelCriteriaBuilder.Operator} and values - * - * @param modelField model field - * @param op operator - * @param values searched values - * @param parameters mapping between named parameters and corresponding values - * @return resulting where clause - */ - public static String produceWhereClause(SearchableModelField modelField, ModelCriteriaBuilder.Operator op, - Object[] values, Map parameters) { - String fieldName = IckleQueryMapModelCriteriaBuilder.getFieldName(modelField); - - if (op == ModelCriteriaBuilder.Operator.ILIKE && !LOWERCASE_NORMALIZED_MODEL_FIELDS.contains(modelField)) { - throw new CriterionNotSupportedException(modelField, op, "Attempt to search case-insensitively without lowercase normalizer applied on the field."); - } - - if (op == ModelCriteriaBuilder.Operator.LIKE && LOWERCASE_NORMALIZED_MODEL_FIELDS.contains(modelField)) { - throw new CriterionNotSupportedException(modelField, op, "Attempt to search case-sensitively with lowercase-normalized field."); - } - - return whereClauseProducerForModelField(modelField).produceWhereClause(fieldName, op, values, parameters); - } - - private static String whereClauseForAttributes(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - if (values == null || values.length != 2) { - throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected attribute_name-value pair, got: " + Arrays.toString(values)); - } - - final Object attrName = values[0]; - if (! (attrName instanceof String)) { - throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values)); - } - - String attrNameS = (String) attrName; - Object[] realValues = new Object[values.length - 1]; - System.arraycopy(values, 1, realValues, 0, values.length - 1); - - // Clause for searching attribute name - String nameClause = IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.EQ, modelFieldName + ".name", new Object[]{attrNameS}, parameters); - // Clause for searching attribute value - String valueClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".values", realValues, parameters); - - return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")"; - } - - private static String whereClauseForUserIdpAlias(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - if (op != ModelCriteriaBuilder.Operator.EQ) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op); - } - if (values == null || values.length == 0 || values.length > 2) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op, "Invalid arguments, expected (idp_alias) or (idp_alias, idp_user), got: " + Arrays.toString(values)); - } - - final Object idpAlias = values[0]; - if (values.length == 1) { - return IckleQueryOperators.combineExpressions(op, modelFieldName + ".identityProvider", values, parameters); - } else if (idpAlias == null) { - final Object idpUserId = values[1]; - return IckleQueryOperators.combineExpressions(op, modelFieldName + ".userId", new Object[] { idpUserId }, parameters); - } else { - final Object idpUserId = values[1]; - // Clause for searching federated identity id - String idClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".identityProvider", new Object[]{ idpAlias }, parameters); - // Clause for searching federated identity userId - String userIdClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".userId", new Object[] { idpUserId }, parameters); - - return "(" + idClause + ")" + " AND " + "(" + userIdClause + ")"; - } - } - - private static String whereClauseForConsentClientFederationLink(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - if (op != ModelCriteriaBuilder.Operator.EQ) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, op); - } - if (values == null || values.length != 1) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, op, "Invalid arguments, expected (federation_provider_id), got: " + Arrays.toString(values)); - } - - String providerId = new StorageId((String) values[0], "").getId(); - return IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.LIKE, getFieldName(UserModel.SearchableFields.CONSENT_FOR_CLIENT), new String[] {providerId + "%"}, parameters); - } - - private static String whereClauseForCorrespondingSessionId(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - if (op != ModelCriteriaBuilder.Operator.EQ) { - throw new CriterionNotSupportedException(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, op); - } - if (values == null || values.length != 1) { - throw new CriterionNotSupportedException(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, op, "Invalid arguments, expected (corresponding_session:id), got: " + Arrays.toString(values)); - } - - // Clause for searching key - String nameClause = IckleQueryOperators.combineExpressions(op, "notes.key", new String[]{UserSessionModel.CORRESPONDING_SESSION_ID}, parameters); - // Clause for searching value - String valueClause = IckleQueryOperators.combineExpressions(op, "notes.value", values, parameters); - - return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")"; - } - - private static String whereClauseForPolicyConfig(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - if (values == null || values.length == 0) { - throw new CriterionNotSupportedException(Policy.SearchableFields.CONFIG, op, "Invalid arguments, expected (config_name, config_value_operator_arguments), got: " + Arrays.toString(values)); - } - - final Object attrName = values[0]; - if (!(attrName instanceof String)) { - throw new CriterionNotSupportedException(Policy.SearchableFields.CONFIG, op, "Invalid arguments, expected (String config_name), got: " + Arrays.toString(values)); - } - - String attrNameS = (String) attrName; - Object[] realValues = new Object[values.length - 1]; - System.arraycopy(values, 1, realValues, 0, values.length - 1); - - boolean isNotExists = op.equals(ModelCriteriaBuilder.Operator.NOT_EXISTS); - if (isNotExists || op.equals(ModelCriteriaBuilder.Operator.EXISTS)) { - ModelCriteriaBuilder.Operator o = isNotExists ? ModelCriteriaBuilder.Operator.NE : ModelCriteriaBuilder.Operator.EQ; - return IckleQueryOperators.combineExpressions(o, modelFieldName + ".key", new String[] { attrNameS }, parameters); - } - - String nameClause = IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.EQ, modelFieldName + ".key", new String[] { attrNameS }, parameters); - - if (realValues.length == 0) { - return nameClause; - } - - String valueClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".value", realValues, parameters); - return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")"; - } - - private static String whereClauseForEnumWithStableIndex(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - if (values != null && values.length == 1) { - if (values[0] instanceof EnumWithStableIndex) { - values[0] = ((EnumWithStableIndex) values[0]).getStableIndex(); - } else if (values[0] instanceof Collection) { - values[0] = ((Collection) values[0]).stream().map(EnumWithStableIndex::getStableIndex).collect(Collectors.toSet()); - } else if (values[0] instanceof Stream) { - values[0] = ((Stream) values[0]).map(EnumWithStableIndex::getStableIndex); - } - } - - return produceWhereClause(modelFieldName, op, values, parameters); - } - - private static String whereClauseForUsernameCaseInsensitive(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) { - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String) { - values[i] = KeycloakModelUtils.toLowerCaseSafe((String) values[i]); - } - } - - return produceWhereClause(modelFieldName, op == ModelCriteriaBuilder.Operator.ILIKE ? ModelCriteriaBuilder.Operator.LIKE : op, values, parameters); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/SingleUseObjectHotRodCrudOperations.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/SingleUseObjectHotRodCrudOperations.java deleted file mode 100644 index b5f65c24391..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/SingleUseObjectHotRodCrudOperations.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod; - -import org.infinispan.client.hotrod.RemoteCache; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.chm.SingleUseObjectModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; -import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity; -import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate; - -import java.util.stream.Stream; - - -/** - * @author Martin Kanis - */ -public class SingleUseObjectHotRodCrudOperations - extends HotRodCrudOperations { - - public SingleUseObjectHotRodCrudOperations(KeycloakSession session, RemoteCache remoteCache, StringKeyConverter keyConverter, - HotRodEntityDescriptor storedEntityDescriptor, - DeepCloner cloner, Long lockTimeout) { - super(session, remoteCache, keyConverter, storedEntityDescriptor, cloner, lockTimeout); - } - - @Override - public HotRodSingleUseObjectEntityDelegate create(HotRodSingleUseObjectEntityDelegate value) { - if (value.getId() == null) { - if (value.getObjectKey() != null) { - value.setId(value.getObjectKey()); - } - } - return super.create(value); - } - - @Override - public Stream read(QueryParameters queryParameters) { - DefaultModelCriteria criteria = queryParameters.getModelCriteriaBuilder(); - - if (criteria == null) { - return Stream.empty(); - } - - SingleUseObjectModelCriteriaBuilder mcb = criteria.flashToModelCriteriaBuilder(createSingleUseObjectCriteriaBuilder()); - if (mcb.isValid()) { - HotRodSingleUseObjectEntityDelegate value = read(mcb.getKey()); - return value != null ? Stream.of(value) : Stream.empty(); - } - - return super.read(queryParameters); - } - - private SingleUseObjectModelCriteriaBuilder createSingleUseObjectCriteriaBuilder() { - return new SingleUseObjectModelCriteriaBuilder(); - } - -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authSession/HotRodAuthenticationSessionEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authSession/HotRodAuthenticationSessionEntity.java deleted file mode 100644 index 9068c71dff9..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authSession/HotRodAuthenticationSessionEntity.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authSession; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; -import org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; -import org.keycloak.sessions.AuthenticationSessionModel; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authSession.MapAuthenticationSessionEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity.AbstractHotRodAuthenticationSessionEntityDelegate" -) -public class HotRodAuthenticationSessionEntity extends AbstractHotRodEntity { - - @ProtoField(number = 1) - public String tabId; - - @ProtoField(number = 2) - public String clientUUID; - - @ProtoField(number = 3) - public String authUserId; - - @ProtoField(number = 4) - public Long timestamp; - - @ProtoField(number = 5) - public String redirectUri; - - @ProtoField(number = 6) - public String action; - - @ProtoField(number = 7) - public Set clientScopes; - - @ProtoField(number = 8) - public Set> executionStatuses; - - @ProtoField(number = 9) - public String protocol; - - @ProtoField(number = 10) - public Set> clientNotes; - - @ProtoField(number = 11) - public Set> authNotes; - - @ProtoField(number = 12) - public Set requiredActions; - - @ProtoField(number = 13) - public Set> userSessionNotes; - - public static abstract class AbstractHotRodAuthenticationSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapAuthenticationSessionEntity { - - @Override - public Map getExecutionStatuses() { - Set> executionStatuses = getHotRodEntity().executionStatuses; - if (executionStatuses == null) { - return Collections.emptyMap(); - } - return executionStatuses.stream().collect(Collectors.toMap(HotRodPair::getKey, - v -> AuthenticationSessionModel.ExecutionStatus.valueOfInteger(v.getValue()))); - } - - @Override - public void setExecutionStatuses(Map executionStatus) { - HotRodAuthenticationSessionEntity hotRodEntity = getHotRodEntity(); - Set> executionStatusSet = executionStatus == null ? null : - executionStatus.entrySet().stream() - .map(e -> new HotRodPair<>(e.getKey(), e.getValue().getStableIndex())) - .collect(Collectors.toSet()); - hotRodEntity.updated |= ! Objects.equals(hotRodEntity.executionStatuses, executionStatusSet); - hotRodEntity.executionStatuses = executionStatusSet; - } - - @Override - public void setExecutionStatus(String authenticator, AuthenticationSessionModel.ExecutionStatus status) { - HotRodAuthenticationSessionEntity hotRodEntity = getHotRodEntity(); - if (hotRodEntity.executionStatuses == null) { - hotRodEntity.executionStatuses = new HashSet<>(); - } - boolean valueUndefined = status == null; - hotRodEntity.updated |= HotRodTypesUtils.removeFromSetByMapKey(hotRodEntity.executionStatuses, authenticator, HotRodTypesUtils::getKey); - hotRodEntity.updated |= !valueUndefined && hotRodEntity.executionStatuses.add(new HotRodPair<>(authenticator, status.getStableIndex())); - } - } - - @Override - public boolean equals(Object o) { - return HotRodAuthenticationSessionEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAuthenticationSessionEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authSession/HotRodRootAuthenticationSessionEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authSession/HotRodRootAuthenticationSessionEntity.java deleted file mode 100644 index e4dcd05b60a..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authSession/HotRodRootAuthenticationSessionEntity.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authSession; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Collections; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity.AbstractHotRodRootAuthenticationSessionEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.sessions.RootAuthenticationSessionModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodRootAuthenticationSessionEntity.VERSION) -public class HotRodRootAuthenticationSessionEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodRootAuthenticationSessionEntity.class, - HotRodAuthenticationSessionEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodRootAuthenticationSessionEntitySchema extends GeneratedSchema { - HotRodRootAuthenticationSessionEntitySchema INSTANCE = new HotRodRootAuthenticationSessionEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @ProtoField(number = 4) - public Long timestamp; - - @ProtoField(number = 5) - public Long expiration; - - @ProtoField(number = 6) - public Set authenticationSessions; - - public static abstract class AbstractHotRodRootAuthenticationSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapRootAuthenticationSessionEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodRootAuthenticationSessionEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public boolean isUpdated() { - HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity(); - return rootAuthSession.updated || - Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationSessionEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity(); - rootAuthSession.updated = false; - Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - } - } - - @Override - public boolean equals(Object o) { - return HotRodRootAuthenticationSessionEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodRootAuthenticationSessionEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodPermissionTicketEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodPermissionTicketEntity.java deleted file mode 100644 index 5835c6cc468..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodPermissionTicketEntity.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authorization; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodPermissionTicketEntity.AbstractHotRodPermissionTicketEntity", - topLevelEntity = true, - modelClass = "org.keycloak.authorization.model.PermissionTicket", - cacheName = "authz" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodPermissionTicketEntity.VERSION) -public class HotRodPermissionTicketEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodPermissionTicketEntity.class, - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE) - public interface HotRodPermissionTicketEntitySchema extends GeneratedSchema { - HotRodPermissionTicketEntitySchema INSTANCE = new HotRodPermissionTicketEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Basic(sortable = true) - @ProtoField(number = 4) - public String owner; - - @Basic(sortable = true) - @ProtoField(number = 5) - public String requester; - - @ProtoField(number = 6) - public Long createdTimestamp; - - @Basic(sortable = true) - @ProtoField(number = 7) - public Long grantedTimestamp; - - @Basic(sortable = true) - @ProtoField(number = 8) - public String resourceId; - - @Basic(sortable = true) - @ProtoField(number = 9) - public String scopeId; - - @Basic(sortable = true) - @ProtoField(number = 10) - public String resourceServerId; - - @Basic(sortable = true) - @ProtoField(number = 11) - public String policyId; - - public static abstract class AbstractHotRodPermissionTicketEntity extends UpdatableHotRodEntityDelegateImpl implements MapPermissionTicketEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodPermissionTicketEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodPermissionTicketEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodPermissionTicketEntityDelegate.entityHashCode(this); - } -} \ No newline at end of file diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodPolicyEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodPolicyEntity.java deleted file mode 100644 index 617598ba886..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodPolicyEntity.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authorization; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodStringPair; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Objects; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authorization.entity.MapPolicyEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodPolicyEntity.AbstractHotRodPolicyEntity", - topLevelEntity = true, - modelClass = "org.keycloak.authorization.model.Policy", - cacheName = "authz" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodPolicyEntity.VERSION) -public class HotRodPolicyEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodPolicyEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodPolicyEntitySchema extends GeneratedSchema { - HotRodPolicyEntitySchema INSTANCE = new HotRodPolicyEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 4) - public String name; - - @ProtoField(number = 5) - public String description; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 6) - public String type; - - @ProtoField(number = 7) - public Integer decisionStrategy; - - @ProtoField(number = 8) - public Integer logic; - - @Basic(sortable = true) - @ProtoField(number = 9) - public Set configs; - - @Basic(sortable = true) - @ProtoField(number = 10) - public String resourceServerId; - - @Basic(sortable = true) - @ProtoField(number = 11) - public Set associatedPolicyIds; - - @Basic(sortable = true) - @ProtoField(number = 12) - public Set resourceIds; - - @Basic(sortable = true) - @ProtoField(number = 13) - public Set scopeIds; - - @Basic(sortable = true) - @ProtoField(number = 14) - public String owner; - - public static abstract class AbstractHotRodPolicyEntity extends UpdatableHotRodEntityDelegateImpl implements MapPolicyEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodPolicyEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setName(String name) { - HotRodPolicyEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.name, name); - entity.name = name; - } - } - - @Override - public boolean equals(Object o) { - return HotRodPolicyEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodPolicyEntityDelegate.entityHashCode(this); - } -} \ No newline at end of file diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodResourceEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodResourceEntity.java deleted file mode 100644 index 90f97fca0b3..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodResourceEntity.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authorization; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Objects; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authorization.entity.MapResourceEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceEntity.AbstractHotRodResourceEntity", - topLevelEntity = true, - modelClass = "org.keycloak.authorization.model.Resource", - cacheName = "authz" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodResourceEntity.VERSION) -public class HotRodResourceEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodResourceEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodResourceEntitySchema extends GeneratedSchema { - HotRodResourceEntitySchema INSTANCE = new HotRodResourceEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 4) - public String name; - - @ProtoField(number = 5) - public String displayName; - - @Basic(sortable = true) - @ProtoField(number = 6) - public Set uris; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 7) - public String type; - - @ProtoField(number = 8) - public String iconUri; - - @Basic(sortable = true) - @ProtoField(number = 9) - public String owner; - - @Basic(sortable = true) - @ProtoField(number = 10) - public Boolean ownerManagedAccess; - - @Basic(sortable = true) - @ProtoField(number = 11) - public String resourceServerId; - - @Basic(sortable = true) - @ProtoField(number = 12) - public Set scopeIds; - - @ProtoField(number = 13) - public Set attributes; - - public static abstract class AbstractHotRodResourceEntity extends UpdatableHotRodEntityDelegateImpl implements MapResourceEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodResourceEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setName(String name) { - HotRodResourceEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.name, name); - entity.name = name; - } - } - - @Override - public boolean equals(Object o) { - return HotRodResourceEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodResourceEntityDelegate.entityHashCode(this); - } -} \ No newline at end of file diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodResourceServerEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodResourceServerEntity.java deleted file mode 100644 index bd244c82357..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodResourceServerEntity.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authorization; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authorization.entity.MapResourceServerEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntity.AbstractHotRodResourceServerEntity", - topLevelEntity = true, - modelClass = "org.keycloak.authorization.model.ResourceServer", - cacheName = "authz" -) -@ProtoDoc("schema-version: " + HotRodResourceServerEntity.VERSION) -@Indexed -public class HotRodResourceServerEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodResourceServerEntity.class, - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE) - public interface HotRodResourceServerEntitySchema extends GeneratedSchema { - HotRodResourceServerEntitySchema INSTANCE = new HotRodResourceServerEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Basic(sortable = true) - @ProtoField(number = 4) - public String clientId; - - @ProtoField(number = 5) - public Boolean allowRemoteResourceManagement; - - @ProtoField(number = 6) - public Integer policyEnforcementMode; - - @ProtoField(number = 7) - public Integer decisionStrategy; - - public static abstract class AbstractHotRodResourceServerEntity extends UpdatableHotRodEntityDelegateImpl implements MapResourceServerEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodResourceServerEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodResourceServerEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodResourceServerEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodScopeEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodScopeEntity.java deleted file mode 100644 index 2028d9499fd..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/authorization/HotRodScopeEntity.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.authorization; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Objects; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.authorization.entity.MapScopeEntity", - inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntity.AbstractHotRodScopeEntity", - topLevelEntity = true, - modelClass = "org.keycloak.authorization.model.Scope", - cacheName = "authz" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodScopeEntity.VERSION) -public class HotRodScopeEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodScopeEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE) - public interface HotRodScopeEntitySchema extends GeneratedSchema { - HotRodScopeEntitySchema INSTANCE = new HotRodScopeEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 4) - public String name; - - @ProtoField(number = 5) - public String displayName; - - @ProtoField(number = 6) - public String iconUri; - - @Basic(sortable = true) - @ProtoField(number = 7) - public String resourceServerId; - - public static abstract class AbstractHotRodScopeEntity extends UpdatableHotRodEntityDelegateImpl implements MapScopeEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodScopeEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setName(String name) { - HotRodScopeEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.name, name); - entity.name = name; - } - } - - @Override - public boolean equals(Object o) { - return HotRodScopeEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodScopeEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java deleted file mode 100644 index c2b2c56526a..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.storage.hotRod.client; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.client.MapClientEntity", - inherits = "org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity.AbstractHotRodClientEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.ClientModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodClientEntity.VERSION) -public class HotRodClientEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodClientEntity.class, - HotRodProtocolMapperEntity.class, - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodClientEntitySchema extends GeneratedSchema { - HotRodClientEntitySchema INSTANCE = new HotRodClientEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 4) - public String clientId; - - @ProtoField(number = 5) - public String name; - - @ProtoField(number = 6) - public String description; - - @ProtoField(number = 7) - public Set redirectUris; - - @ProtoField(number = 8) - public Boolean enabled; - - @ProtoField(number = 9) - public Boolean alwaysDisplayInConsole; - - @ProtoField(number = 10) - public String clientAuthenticatorType; - - @ProtoField(number = 11) - public String secret; - - @ProtoField(number = 12) - public String registrationToken; - - @ProtoField(number = 13) - public String protocol; - - @Basic(sortable = true) - @ProtoField(number = 14) - public Set attributes; - - @ProtoField(number = 15) - public Set> authenticationFlowBindingOverrides; - - @ProtoField(number = 16) - public Boolean publicClient; - - @ProtoField(number = 17) - public Boolean fullScopeAllowed; - - @ProtoField(number = 18) - public Boolean frontchannelLogout; - - @ProtoField(number = 19) - public Long notBefore; - - @ProtoField(number = 20) - public Set scope; - - @ProtoField(number = 21) - public Set webOrigins; - - @ProtoField(number = 22) - public Set protocolMappers; - - @ProtoField(number = 23) - public Set> clientScopes; - - @Basic(sortable = true) - @ProtoField(number = 24, collectionImplementation = LinkedList.class) - public Collection scopeMappings; - - @ProtoField(number = 25) - public Boolean surrogateAuthRequired; - - @ProtoField(number = 26) - public String managementUrl; - - @ProtoField(number = 27) - public String baseUrl; - - @ProtoField(number = 28) - public Boolean bearerOnly; - - @ProtoField(number = 29) - public Boolean consentRequired; - - @ProtoField(number = 30) - public String rootUrl; - - @ProtoField(number = 31) - public Boolean standardFlowEnabled; - - @ProtoField(number = 32) - public Boolean implicitFlowEnabled; - - @ProtoField(number = 33) - public Boolean directAccessGrantsEnabled; - - @ProtoField(number = 34) - public Boolean serviceAccountsEnabled; - - @ProtoField(number = 35) - public Integer nodeReRegistrationTimeout; - - public static abstract class AbstractHotRodClientEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapClientEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodClientEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setClientId(String clientId) { - HotRodClientEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.clientId, clientId); - entity.clientId = clientId; - } - - @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(Map.Entry::getKey); - } - } - - @Override - public boolean equals(Object o) { - return HotRodClientEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodClientEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java deleted file mode 100644 index ceb5b325bee..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.storage.hotRod.client; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; - -import java.util.Set; - -@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.client.MapProtocolMapperEntity") -public class HotRodProtocolMapperEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public String name; - @ProtoField(number = 3) - public String protocol; - @ProtoField(number = 4) - public String protocolMapper; - @ProtoField(number = 5) - public Set> config; - - @Override - public boolean equals(Object o) { - return HotRodProtocolMapperEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodProtocolMapperEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/clientscope/HotRodClientScopeEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/clientscope/HotRodClientScopeEntity.java deleted file mode 100644 index c8e219f5380..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/clientscope/HotRodClientScopeEntity.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.clientscope; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity; -import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Collection; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.clientscope.MapClientScopeEntity", - inherits = "org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity.AbstractHotRodClientScopeEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.ClientScopeModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodClientScopeEntity.VERSION) -public class HotRodClientScopeEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodClientScopeEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class, HotRodClientEntity.HotRodClientEntitySchema.class} - ) - public interface HotRodClientScopeEntitySchema extends GeneratedSchema { - HotRodClientScopeEntitySchema INSTANCE = new HotRodClientScopeEntitySchemaImpl(); - } - - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Basic(sortable = true) - @ProtoField(number = 4) - public String name; - - @ProtoField(number = 5) - public String protocol; - - @ProtoField(number = 6) - public String description; - - @ProtoField(number = 7) - public Collection scopeMappings; - - @ProtoField(number = 8) - public Set protocolMappers; - - @ProtoField(number = 9) - public Set attributes; - - public static abstract class AbstractHotRodClientScopeEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapClientScopeEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodClientScopeEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodClientScopeEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodClientScopeEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/AbstractHotRodEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/AbstractHotRodEntity.java deleted file mode 100644 index 74bb3c5d717..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/AbstractHotRodEntity.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.storage.hotRod.common; - -import org.keycloak.models.map.common.UpdatableEntity; - -public abstract class AbstractHotRodEntity implements UpdatableEntity { - public boolean updated; - - @Override - public boolean isUpdated() { - return this.updated; - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/CommonPrimitivesProtoSchemaInitializer.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/CommonPrimitivesProtoSchemaInitializer.java deleted file mode 100644 index 7c1985ed4dc..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/CommonPrimitivesProtoSchemaInitializer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; - -/** - * @author Martin Kanis - */ -@AutoProtoSchemaBuilder( - includeClasses = { - HotRodPair.class, - HotRodStringPair.class, - HotRodAttributeEntity.class, - HotRodAttributeEntityNonIndexed.class - }, - schemaFileName = "CommonPrimitivesProtoSchema.proto", - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE) -public interface CommonPrimitivesProtoSchemaInitializer extends GeneratedSchema { - String HOT_ROD_ENTITY_PACKAGE = "kc"; - int COMMON_PRIMITIVES_VERSION = 1; - - CommonPrimitivesProtoSchemaInitializer INSTANCE = new CommonPrimitivesProtoSchemaInitializerImpl(); -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntity.java deleted file mode 100644 index 21c662ca727..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntity.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.annotations.ProtoField; - -import java.util.List; -import java.util.Objects; - -/** - * !!! Please do not change this class !!! - * - * If some change is needed please create a new version of this class and solve the migration on top-level entities. - * - */ -@Indexed -public class HotRodAttributeEntity { - @Basic(sortable = true) - @ProtoField(number = 1) - public String name; - - @Basic(sortable = true) - @ProtoField(number = 2) - public List values; - - public HotRodAttributeEntity() { - } - - public HotRodAttributeEntity(String name, List values) { - this.name = name; - this.values = values; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getValues() { - return values; - } - - public void setValues(List values) { - this.values = values; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HotRodAttributeEntity that = (HotRodAttributeEntity) o; - return Objects.equals(name, that.name) && Objects.equals(values, that.values); - } - - @Override - public int hashCode() { - return Objects.hash(name, values); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntityNonIndexed.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntityNonIndexed.java deleted file mode 100644 index 844fab8e132..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntityNonIndexed.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.infinispan.protostream.annotations.ProtoField; - -import java.util.List; -import java.util.Objects; - -/** - * !!! Please do not change this class !!! - * - * If some change is needed please create a new version of this class and solve the migration on top-level entities. - * - */ -public class HotRodAttributeEntityNonIndexed { - @ProtoField(number = 1) - public String name; - - @ProtoField(number = 2) - public List values; - - public HotRodAttributeEntityNonIndexed() { - } - - public HotRodAttributeEntityNonIndexed(String name, List values) { - this.name = name; - this.values = values; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getValues() { - return values; - } - - public void setValues(List values) { - this.values = values; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HotRodAttributeEntityNonIndexed that = (HotRodAttributeEntityNonIndexed) o; - return Objects.equals(name, that.name) && Objects.equals(values, that.values); - } - - @Override - public int hashCode() { - return Objects.hash(name, values); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDelegate.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDelegate.java deleted file mode 100644 index 830d2212479..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDelegate.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.keycloak.models.map.common.UpdatableEntity; - -public interface HotRodEntityDelegate extends UpdatableEntity { - E getHotRodEntity(); -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java deleted file mode 100644 index 196c067b07c..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.storage.hotRod.common; - -import org.infinispan.protostream.GeneratedSchema; - -import java.util.function.Function; - -public interface HotRodEntityDescriptor> { - - Class getModelTypeClass(); - - Class getEntityTypeClass(); - - String getCacheName(); - - Function getHotRodDelegateProvider(); - - Integer getCurrentVersion(); - - GeneratedSchema getProtoSchema(); -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java deleted file mode 100644 index 220da0d386d..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.infinispan.protostream.WrappedMessage; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; - -import java.util.Objects; - -import static org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer.COMMON_PRIMITIVES_VERSION; - -/** - * !!! Please do not change this class !!! - * - * If some change is needed please create a new version of this class and solve the migration on top-level entities. - * - */ -@ProtoDoc("schema-version: " + COMMON_PRIMITIVES_VERSION) -public class HotRodPair { - - @ProtoField(number = 1) - public WrappedMessage key; - @ProtoField(number = 2) - public WrappedMessage value; - - public HotRodPair() {} - - public HotRodPair(T first, V second) { - this.key = new WrappedMessage(first); - this.value = new WrappedMessage(second); - } - - public T getKey() { - return key == null ? null : (T) key.getValue(); - } - - public V getValue() { - return value == null ? null : (V) value.getValue(); - } - - public void setKey(T first) { - this.key = new WrappedMessage(first); - } - - public void setValue(V second) { - this.value = new WrappedMessage(second); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HotRodPair that = (HotRodPair) o; - return Objects.equals(key, that.key) && Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(key, value); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodStringPair.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodStringPair.java deleted file mode 100644 index d22ff6f1aff..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodStringPair.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.annotations.ProtoField; - -import java.util.Objects; - -/** - * !!! Please do not change this class !!! - * - * If some change is needed please create a new version of this class and solve the migration on top-level entities. - * - * Indexed Hot Rod pair entity where both key and value are {@link String} type. The entity should be used when - * there is a need to search by key or/and value. Otherwise {@link HotRodPair} should be used. - */ -@Indexed -public class HotRodStringPair { - - @Basic(sortable = true) - @ProtoField(number = 1) - public String key; - - @Basic(sortable = true) - @ProtoField(number = 2) - public String value; - - public HotRodStringPair() {} - - public HotRodStringPair(String key, String value) { - this.key = key; - this.value = value; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - public void setKey(String key) { - this.key = key; - } - - public void setValue(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HotRodStringPair that = (HotRodStringPair) o; - return Objects.equals(key, that.key) && Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(key, value); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java deleted file mode 100644 index 1db4fc7bb08..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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.storage.hotRod.common; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodLocalizationTexts; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity; -import org.keycloak.models.map.storage.hotRod.userSession.AuthenticatedClientSessionReferenceOnlyFieldDelegate; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntityReference; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class HotRodTypesUtils { - - public static Set migrateMapToSet(Map map, Function, SetValue> creator) { - return map == null ? null : map.entrySet() - .stream() - .map(creator) - .collect(Collectors.toSet()); - } - - public static Map migrateSetToMap(Set set, Function keyProducer, Function valueProducer) { - return set == null ? null : set.stream().collect(HashMap::new, (m, v) -> m.put(keyProducer.apply(v), valueProducer.apply(v)), HashMap::putAll); - } - - public static HotRodPair createHotRodPairFromMapEntry(Map.Entry entry) { - return new HotRodPair<>(entry.getKey(), entry.getValue()); - } - - public static HotRodStringPair createHotRodStringPairFromMapEntry(Map.Entry entry) { - return new HotRodStringPair(entry.getKey(), entry.getValue()); - } - - public static HotRodAttributeEntity createHotRodAttributeEntityFromMapEntry(Map.Entry> entry) { - return new HotRodAttributeEntity(entry.getKey(), entry.getValue()); - } - - public static HotRodAttributeEntityNonIndexed createHotRodAttributeEntityNonIndexedFromMapEntry(Map.Entry> entry) { - return new HotRodAttributeEntityNonIndexed(entry.getKey(), entry.getValue()); - } - - public static boolean removeFromSetByMapKey(Set set, KeyType key, Function keyGetter) { - if (set == null || set.isEmpty()) { return false; } - return set.stream() - .filter(entry -> Objects.equals(keyGetter.apply(entry), key)) - .findFirst() - .map(set::remove) - .orElse(false); - } - - public static MapValue getMapValueFromSet(Set set, MapKey key, Function keyGetter, Function valueGetter) { - return set == null ? null : set.stream().filter(entry -> Objects.equals(keyGetter.apply(entry), key)).findFirst().map(valueGetter).orElse(null); - } - - public static K getKey(HotRodPair hotRodPair) { - return hotRodPair.getKey(); - } - - public static V getValue(HotRodPair hotRodPair) { - return hotRodPair.getValue(); - } - - public static String getKey(HotRodStringPair hotRodPair) { - return hotRodPair.getKey(); - } - - public static String getValue(HotRodStringPair hotRodPair) { - return hotRodPair.getValue(); - } - - public static String getKey(HotRodAttributeEntity attributeEntity) { - return attributeEntity.name; - } - - public static String getKey(HotRodAttributeEntityNonIndexed attributeEntity) { - return attributeEntity.name; - } - - public static List getValue(HotRodAttributeEntity attributeEntity) { - return attributeEntity.values; - } - - public static List getValue(HotRodAttributeEntityNonIndexed attributeEntity) { - return attributeEntity.values; - } - - public static String getKey(AbstractEntity entity) { - return entity.getId(); - } - - public static String getKey(HotRodUserFederatedIdentityEntity hotRodUserFederatedIdentityEntity) { - return hotRodUserFederatedIdentityEntity.identityProvider; - } - - public static String getKey(HotRodUserConsentEntity hotRodUserConsentEntity) { - return hotRodUserConsentEntity.clientId; - } - - public static List migrateList(List p0, Function migrator) { - return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toList()); - } - - public static Set migrateSet(Set p0, Function migrator) { - return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toSet()); - } - - public static String getKey(HotRodAuthenticationSessionEntity hotRodAuthenticationSessionEntity) { - return hotRodAuthenticationSessionEntity.tabId; - } - - public static String getKey(HotRodLocalizationTexts hotRodLocalizationTexts) { - return hotRodLocalizationTexts.getLocale(); - } - - public static Map getValue(HotRodLocalizationTexts hotRodLocalizationTexts) { - Set> values = hotRodLocalizationTexts.getValues(); - return values == null ? null : values.stream().collect(Collectors.toMap(HotRodPair::getKey, HotRodPair::getValue)); - } - - public static HotRodLocalizationTexts migrateStringMapToHotRodLocalizationTexts(String p0, Map p1) { - HotRodLocalizationTexts hotRodLocalizationTexts = new HotRodLocalizationTexts(); - hotRodLocalizationTexts.setLocale(p0); - hotRodLocalizationTexts.setValues(migrateMapToSet(p1, HotRodTypesUtils::createHotRodPairFromMapEntry)); - - return hotRodLocalizationTexts; - } - - public static HotRodAuthenticatedClientSessionEntityReference migrateMapAuthenticatedClientSessionEntityToHotRodAuthenticatedClientSessionEntityReference(MapAuthenticatedClientSessionEntity p0) { - return new HotRodAuthenticatedClientSessionEntityReference(p0.getClientId(), p0.getId()); - } - - public static MapAuthenticatedClientSessionEntity migrateHotRodAuthenticatedClientSessionEntityReferenceToMapAuthenticatedClientSessionEntity(HotRodAuthenticatedClientSessionEntityReference collectionItem) { - return DeepCloner.DUMB_CLONER.entityFieldDelegate(MapAuthenticatedClientSessionEntity.class, new AuthenticatedClientSessionReferenceOnlyFieldDelegate(collectionItem)); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodUtils.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodUtils.java deleted file mode 100644 index 2a01cc17c16..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.storage.hotRod.common; - -import org.infinispan.query.dsl.Query; - -/** - * @author Martin Kanis - */ -public class HotRodUtils { - public static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE >> 1; - public static Query paginateQuery(Query query, Integer first, Integer max) { - if (first != null && first > 0) { - query = query.startOffset(first); - } - - if (max != null && max >= 0) { - query = query.maxResults(max); - } else { - // Infinispan uses default max value equal to 100 - // We need to change this to support more returned values - query = query.maxResults(DEFAULT_MAX_RESULTS); - } - - return query; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodVersionUtils.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodVersionUtils.java deleted file mode 100644 index 735f7fbd33e..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodVersionUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class HotRodVersionUtils { - - private static final Pattern schemaVersionPattern = Pattern.compile("schema-version: (\\d+)$", Pattern.MULTILINE); - - /** - * Decides whether {@code version1} is older than {@code version2} - * - * @param version1 first version - * @param version2 second version - * @return returns true when {@code version1} is older than {@code version2} and false when versions are equal - * or {@code version2} is older than {@code version1} - */ - public static boolean isVersion2NewerThanVersion1(Integer version1, Integer version2) { - return version1 < version2; - } - - /** - * Decides whether {@code version1} and {@code version2} are adjacent values (there are no versions between these) - * - * @param version1 first version - * @param version2 second version - * @return returns true when {@code version1} and {@code version2} are adjacent, false otherwise - */ - public static boolean adjacentVersions(Integer version1, Integer version2) { - return Math.abs(version1 - version2) == 1; - } - - /** - * Searches given {@code protoFile} string for occurrences of string schema-version: X, where X is version of current - * schema in the {@code protoFile} string - * - * @param protoFile schema to search - * @return Integer object representing version of schema within {@code protoFile} or {@code null} if not found - * @throws IllegalStateException if file contains more than one version definitions - */ - public static Integer parseSchemaVersionFromProtoFile(String protoFile) { - Matcher matcher = schemaVersionPattern.matcher(protoFile); - - if (matcher.find()) { - if (matcher.groupCount() > 1) { - throw new IllegalStateException("More than one occurrence of schema-version definitions within one proto file " + protoFile); - } - return Integer.parseInt(matcher.group(1)); - } - - return null; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/UpdatableHotRodEntityDelegateImpl.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/UpdatableHotRodEntityDelegateImpl.java deleted file mode 100644 index f02a31f1515..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/UpdatableHotRodEntityDelegateImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.common; - -import org.keycloak.models.map.common.UpdatableEntity; - -public abstract class UpdatableHotRodEntityDelegateImpl implements HotRodEntityDelegate { - - @Override - public boolean isUpdated() { - return getHotRodEntity().isUpdated(); - } - - @Override - public void clearUpdatedFlag() { - getHotRodEntity().clearUpdatedFlag(); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProvider.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProvider.java deleted file mode 100644 index 464b6041a77..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.storage.hotRod.connections; - -import org.infinispan.client.hotrod.RemoteCache; -import org.infinispan.client.hotrod.RemoteCacheManager; - -/** - * @author Martin Kanis - */ -public class DefaultHotRodConnectionProvider implements HotRodConnectionProvider { - - private RemoteCacheManager remoteCacheManager; - - public DefaultHotRodConnectionProvider(RemoteCacheManager remoteCacheManager) { - this.remoteCacheManager = remoteCacheManager; - } - - @Override - public RemoteCache getRemoteCache(String name) { - return remoteCacheManager.getCache(name); - } - - @Override - public void close() { - - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java deleted file mode 100644 index c6b43a064d6..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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.storage.hotRod.connections; - -import org.infinispan.client.hotrod.RemoteCache; -import org.infinispan.client.hotrod.RemoteCacheManager; -import org.infinispan.client.hotrod.RemoteCacheManagerAdmin; -import org.infinispan.client.hotrod.configuration.ClientIntelligence; -import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; -import org.infinispan.client.hotrod.configuration.NearCacheMode; -import org.infinispan.client.hotrod.configuration.TransactionMode; -import org.infinispan.commons.marshall.ProtoStreamMarshaller; -import org.infinispan.commons.tx.lookup.TransactionManagerLookup; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; -import org.jboss.logging.Logger; -import org.keycloak.common.Profile; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils; -import org.keycloak.models.map.storage.hotRod.transaction.HotRodTransactionManagerLookup; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Duration; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import static org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP; -import static org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils.adjacentVersions; -import static org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils.isVersion2NewerThanVersion1; - -/** - * @author Martin Kanis - */ -public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionProviderFactory, EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "default"; - public static final String SCRIPT_CACHE = "___script_cache"; - public static final String HOT_ROD_LOCKS_CACHE_NAME = "locks"; - private static final String HOT_ROD_INIT_LOCK_NAME = "HOT_ROD_INIT_LOCK"; - private static final Logger LOG = Logger.getLogger(DefaultHotRodConnectionProviderFactory.class); - - private org.keycloak.Config.Scope config; - - private volatile RemoteCacheManager remoteCacheManager; - - private TransactionManagerLookup transactionManagerLookup; - - @Override - public HotRodConnectionProvider create(KeycloakSession session) { - if (remoteCacheManager == null) { - synchronized (this) { - if (remoteCacheManager == null) { - lazyInit(session); - } - } - } - return new DefaultHotRodConnectionProvider(remoteCacheManager); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - if (remoteCacheManager != null) { - remoteCacheManager.close(); - remoteCacheManager = null; - } - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public void init(org.keycloak.Config.Scope config) { - this.config = config; - } - - public void lazyInit(KeycloakSession session) { - LOG.debugf("Initializing HotRod client connection to Infinispan server."); - transactionManagerLookup = new HotRodTransactionManagerLookup(session); - - ConfigurationBuilder remoteBuilder = new ConfigurationBuilder(); - remoteBuilder.addServer() - .host(config.get("host", "localhost")) - .port(config.getInt("port", 11222)) - .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE) - .marshaller(new ProtoStreamMarshaller()); - - if (config.getBoolean("enableSecurity", true)) { - remoteBuilder.security() - .authentication() - .saslMechanism("SCRAM-SHA-512") - .username(config.get("username", "admin")) - .password(config.get("password", "admin")) - .realm(config.get("realm", "default")); - } - - LOG.debugf("Configuring remote caches."); - configureRemoteCaches(remoteBuilder); - - remoteBuilder.addContextInitializer(CommonPrimitivesProtoSchemaInitializer.INSTANCE); - ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getProtoSchema).forEach(remoteBuilder::addContextInitializer); - - // Configure settings necessary for locking - configureLocking(remoteBuilder); - - remoteCacheManager = new RemoteCacheManager(remoteBuilder.build()); - - // Acquire initial phase lock to avoid concurrent schema update - RemoteCache locksCache = remoteCacheManager.getCache(HOT_ROD_LOCKS_CACHE_NAME); - try { - HotRodLocksUtils.repeatPutIfAbsent(locksCache, HOT_ROD_INIT_LOCK_NAME, Duration.ofMillis(900), 50, false); - - Set remoteCaches = ENTITY_DESCRIPTOR_MAP.values().stream() - .map(HotRodEntityDescriptor::getCacheName).collect(Collectors.toSet()); - - LOG.debugf("Uploading proto schema to Infinispan server."); - registerSchemata(); - - String reindexCaches = config.get("reindexCaches", null); - RemoteCacheManagerAdmin administration = remoteCacheManager.administration(); - if (reindexCaches != null && reindexCaches.equals("all")) { - LOG.infof("Reindexing all caches. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results."); - remoteCaches.stream() - .peek(remoteCacheManager::getCache) // access the caches to force their creation, otherwise reindexing fails if cache doesn't exist - .forEach(administration::reindexCache); - } else if (reindexCaches != null && !reindexCaches.isEmpty()) { - Arrays.stream(reindexCaches.split(",")) - .map(String::trim) - .filter(e -> !e.isEmpty()) - .filter(remoteCaches::contains) - .peek(cacheName -> LOG.infof("Reindexing %s cache. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.", cacheName)) - .peek(remoteCacheManager::getCache) // access the caches to force their creation, otherwise reindexing fails if cache doesn't exist - .forEach(administration::reindexCache); - } - - LOG.infof("HotRod client configuration was successful."); - } finally { - if (!HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, HOT_ROD_INIT_LOCK_NAME)) { - throw new RuntimeException("Cannot release HotRod init lock"); - } - } - } - - private void configureLocking(ConfigurationBuilder builder) { - builder.remoteCache(HOT_ROD_LOCKS_CACHE_NAME) - .configurationURI(getCacheConfigUri(HOT_ROD_LOCKS_CACHE_NAME)); - } - - private void registerSchemata() { - final RemoteCache protoMetadataCache = remoteCacheManager.getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); - Set cachesForIndexUpdate = new HashSet<>(); - - // First add Common classes definitions - GeneratedSchema commonSchema = CommonPrimitivesProtoSchemaInitializer.INSTANCE; - String currentProtoFile = protoMetadataCache.get(commonSchema.getProtoFileName()); - // there is no proto file deployed on the server - if (currentProtoFile == null) { - protoMetadataCache.put(commonSchema.getProtoFileName(), commonSchema.getProtoFile()); - } - else if (isUpdateNeeded(commonSchema.getProtoFileName(), CommonPrimitivesProtoSchemaInitializer.COMMON_PRIMITIVES_VERSION, currentProtoFile)) { - protoMetadataCache.put(commonSchema.getProtoFileName(), commonSchema.getProtoFile()); - - // if there is a change in common primitives, update all caches as we don't track in what areas are these common primitives used - cachesForIndexUpdate = ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getCacheName).collect(Collectors.toSet()); - } - - // Add schema for each entity descriptor - for (HotRodEntityDescriptor descriptor : ENTITY_DESCRIPTOR_MAP.values()) { - GeneratedSchema schema = descriptor.getProtoSchema(); - currentProtoFile = protoMetadataCache.get(schema.getProtoFileName()); - // there is no proto file deployed on the server - if (currentProtoFile == null) { - protoMetadataCache.put(schema.getProtoFileName(), schema.getProtoFile()); - } - else if (isUpdateNeeded(schema.getProtoFileName(), descriptor.getCurrentVersion(), currentProtoFile)) { - protoMetadataCache.put(schema.getProtoFileName(), schema.getProtoFile()); - cachesForIndexUpdate.add(descriptor.getCacheName()); - } - } - - String errors = protoMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX); - if (errors != null) { - for (String errorFile : errors.split("\n")) { - LOG.errorf("\nThere was an error in proto file: %s\n" + - "Error message: %s\n" + - "Current proto schema: %s", - errorFile, - protoMetadataCache.get(errorFile + ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX), - protoMetadataCache.get(errorFile)); - } - - throw new IllegalStateException("Some Protobuf schema files contain errors: " + errors); - } - - // update index schema for caches, where a proto schema was updated - RemoteCacheManagerAdmin administration = remoteCacheManager.administration(); - cachesForIndexUpdate.forEach(administration::updateIndexSchema); - } - - /** - * Decides whether the schema should be updated - * @return true if schema in the server is obsolete or doesn't exist, false otherwise - * @throws IllegalStateException when schema in the server is older than one version behind - */ - private boolean isUpdateNeeded(String protoFileName, int currentSchemaVersion, String currentProtoFileDeployed) { - // If there is no proto file deployed in the server return true - if (currentProtoFileDeployed == null) return true; - - // Parse currently deployed schema version - Integer deployedSchemaVersion = HotRodVersionUtils.parseSchemaVersionFromProtoFile(currentProtoFileDeployed); - if (deployedSchemaVersion == null) { - LOG.errorf("Schema %s does not contain expected schema-version definition:\n%s", protoFileName, currentProtoFileDeployed); - throw new IllegalStateException("Deployed schema " + protoFileName + " does not contain expected schema-version definition. See log for more details."); - } - - if (currentSchemaVersion != deployedSchemaVersion && !adjacentVersions(deployedSchemaVersion, currentSchemaVersion)) { - // Infinispan server contains schema that is older than {current_version - 1}, upgrade needs to be done sequentially - throw new IllegalStateException("Infinispan server contains too old schema version for " + protoFileName); - } - - return isVersion2NewerThanVersion1(deployedSchemaVersion, currentSchemaVersion); - } - - private void configureRemoteCaches(ConfigurationBuilder builder) { - Consumer configurator = configurationBuilderConsumer(builder); - - ENTITY_DESCRIPTOR_MAP.values().stream() - .map(HotRodEntityDescriptor::getCacheName) - .distinct() - .forEach(configurator); - } - - private static URI getCacheConfigUri(String cacheName) { - try { - return DefaultHotRodConnectionProviderFactory.class.getClassLoader().getResource("config/" + cacheName + "-cache-config.xml").toURI(); - } catch (URISyntaxException e) { - throw new RuntimeException("Cannot read the cache configuration for cache + " + cacheName, e); - } - } - - private Consumer configurationBuilderConsumer(ConfigurationBuilder builder) { - return cacheName -> { - LOG.debugf("Configuring cache %s", cacheName); - builder.remoteCache(cacheName) - .configurationURI(getCacheConfigUri(cacheName)) - .transactionMode(TransactionMode.FULL_XA) - .transactionManagerLookup(transactionManagerLookup) - .nearCacheMode(config.scope(cacheName).getBoolean("nearCacheEnabled", config.getBoolean("nearCacheEnabled", true)) ? NearCacheMode.INVALIDATED : NearCacheMode.DISABLED) - .nearCacheMaxEntries(config.scope(cacheName).getInt("nearCacheMaxEntries", config.getInt("nearCacheMaxEntries", 10000))) - .nearCacheUseBloomFilter(config.scope(cacheName).getBoolean("nearCacheBloomFilter", config.getBoolean("nearCacheBloomFilter", false))); - }; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionProvider.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionProvider.java deleted file mode 100644 index 4cd44ab5542..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.storage.hotRod.connections; - -import org.infinispan.client.hotrod.RemoteCache; -import org.keycloak.provider.Provider; - -/** - * @author Martin Kanis - */ -public interface HotRodConnectionProvider extends Provider { - - /** - * Returns a remote Infinispan cache specified by the given name. - * @param name String Name of the remote cache. - * @param key - * @param value - * @return A remote Infinispan cache specified by name. - */ - RemoteCache getRemoteCache(String name); -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionProviderFactory.java deleted file mode 100644 index 03ca08f6387..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionProviderFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.storage.hotRod.connections; - -import org.keycloak.provider.ProviderFactory; - -/** - * @author Martin Kanis - */ -public interface HotRodConnectionProviderFactory extends ProviderFactory { -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionSpi.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionSpi.java deleted file mode 100644 index 780839df6c4..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/HotRodConnectionSpi.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.storage.hotRod.connections; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author Martin Kanis - */ -public class HotRodConnectionSpi implements Spi { - - public static final String NAME = "connectionsHotRod"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return HotRodConnectionProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return HotRodConnectionProviderFactory.class; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/events/HotRodAdminEventEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/events/HotRodAdminEventEntity.java deleted file mode 100644 index 0eb6e96e3f9..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/events/HotRodAdminEventEntity.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.events; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.events.MapAdminEventEntity", - inherits = "org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntity.AbstractHotRodAdminEventEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.events.admin.AdminEvent" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodAdminEventEntity.VERSION) -public class HotRodAdminEventEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodAdminEventEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE) - public interface HotRodAdminEventEntitySchema extends GeneratedSchema { - HotRodAdminEventEntitySchema INSTANCE = new HotRodAdminEventEntitySchemaImpl(); - } - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @ProtoField(number = 3) - public Long expiration; - - @Basic(sortable = true) - @ProtoField(number = 4) - public Long timestamp; - - @Basic(sortable = true) - @ProtoField(number = 5) - public Integer operationType; - - @Basic(sortable = true) - @ProtoField(number = 6) - public String authClientId; - - @Basic(sortable = true) - @ProtoField(number = 7) - public String authIpAddress; - - @Basic(sortable = true) - @ProtoField(number = 8) - public String authRealmId; - - @Basic(sortable = true) - @ProtoField(number = 9) - public String authUserId; - - @ProtoField(number = 10) - public String error; - - @Basic(sortable = true) - @ProtoField(number = 11) - public String realmId; - - @ProtoField(number = 12) - public String representation; - - @Basic(sortable = true) - @ProtoField(number = 13) - public String resourcePath; - - @Basic(sortable = true) - @ProtoField(number = 14) - public String resourceType; - - public static abstract class AbstractHotRodAdminEventEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapAdminEventEntity { - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodAdminEventEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodAdminEventEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAdminEventEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/events/HotRodAuthEventEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/events/HotRodAuthEventEntity.java deleted file mode 100644 index df1f03e1cbb..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/events/HotRodAuthEventEntity.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.events; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Set; -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.events.MapAuthEventEntity", - inherits = "org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntity.AbstractHotRodAuthEventEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.events.Event" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodAuthEventEntity.VERSION) -public class HotRodAuthEventEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodAuthEventEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodAuthEventEntitySchema extends GeneratedSchema { - HotRodAuthEventEntitySchema INSTANCE = new HotRodAuthEventEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public Integer type; - - @ProtoField(number = 4) - public Long expiration; - - @Basic(sortable = true) - @ProtoField(number = 5) - public Long timestamp; - - @Basic(sortable = true) - @ProtoField(number = 6) - public String clientId; - - @ProtoField(number = 7) - public String error; - - @Basic(sortable = true) - @ProtoField(number = 8) - public String ipAddress; - - @Basic(sortable = true) - @ProtoField(number = 9) - public String realmId; - - @ProtoField(number = 10) - public String sessionId; - - @Basic(sortable = true) - @ProtoField(number = 11) - public String userId; - - @ProtoField(number = 12) - public Set> details; - - public static abstract class AbstractHotRodAuthEventEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapAuthEventEntity { - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodAuthEventEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodAuthEventEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAuthEventEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/group/HotRodGroupEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/group/HotRodGroupEntity.java deleted file mode 100644 index 315304f052c..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/group/HotRodGroupEntity.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.storage.hotRod.group; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Objects; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.group.MapGroupEntity", - inherits = "org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity.AbstractHotRodGroupEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.GroupModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodGroupEntity.VERSION) -public class HotRodGroupEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodGroupEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodGroupEntitySchema extends GeneratedSchema { - HotRodGroupEntitySchema INSTANCE = new HotRodGroupEntitySchemaImpl(); - } - - public static abstract class AbstractHotRodGroupEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapGroupEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodGroupEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setName(String name) { - HotRodGroupEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.name, name); - entity.name = name; - } - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 4) - public String name; - - @Basic(sortable = true) - @ProtoField(number = 5) - public String parentId; - - @Basic(sortable = true) - @ProtoField(number = 6) - public Set attributes; - - @Basic(sortable = true) - @ProtoField(number = 7) - public Set grantedRoles; - - @Override - public boolean equals(Object o) { - return HotRodGroupEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodGroupEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodGlobalLockProvider.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodGlobalLockProvider.java deleted file mode 100644 index 12b309503cb..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodGlobalLockProvider.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.locking; - -import org.infinispan.client.hotrod.RemoteCache; -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionTaskWithResult; -import org.keycloak.models.locking.GlobalLockProvider; -import org.keycloak.models.locking.LockAcquiringTimeoutException; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.time.Duration; -import java.util.Objects; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; - - -public class HotRodGlobalLockProvider implements GlobalLockProvider { - - private static final Logger LOG = Logger.getLogger(HotRodGlobalLockProvider.class); - private final KeycloakSession session; - private final RemoteCache locksCache; - private final long defaultTimeoutMilliseconds; - - public HotRodGlobalLockProvider(KeycloakSession session, RemoteCache locksCache, long defaultTimeoutMilliseconds) { - this.locksCache = locksCache; - this.defaultTimeoutMilliseconds = defaultTimeoutMilliseconds; - this.session = session; - } - - @Override - public V withLock(String lockName, Duration timeToWaitForLock, KeycloakSessionTaskWithResult task) throws LockAcquiringTimeoutException { - Objects.requireNonNull(lockName, "lockName cannot be null"); - - if (timeToWaitForLock == null) { - // Set default timeout if null provided - timeToWaitForLock = Duration.ofMillis(defaultTimeoutMilliseconds); - } - - try { - LOG.debugf("Acquiring lock [%s].%s", lockName, getShortStackTrace()); - HotRodLocksUtils.repeatPutIfAbsent(locksCache, lockName, timeToWaitForLock, 50, false); - LOG.debugf("Lock acquired [%s]. Continuing with task execution.", lockName); - - return KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), task); - } finally { - LOG.debugf("Releasing lock [%s].%s", lockName, getShortStackTrace()); - boolean result = HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, lockName); - LOG.debugf("Lock [%s] release resulted with %s", lockName, result); - } - } - - @Override - public void forceReleaseAllLocks() { - locksCache.clear(); - } - - @Override - public void close() { - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodGlobalLockProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodGlobalLockProviderFactory.java deleted file mode 100644 index e3d359a34fe..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodGlobalLockProviderFactory.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.locking; - -import org.infinispan.client.hotrod.RemoteCache; -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.locking.GlobalLockProvider; -import org.keycloak.models.locking.GlobalLockProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -public class HotRodGlobalLockProviderFactory implements GlobalLockProviderFactory, EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "hotrod"; - protected static final String HOT_ROD_LOCKS_CACHE = "locks"; - - private RemoteCache locksCache; - private long defaultTimeoutMilliseconds; - - - @Override - public GlobalLockProvider create(KeycloakSession session) { - if (locksCache == null) { - lazyInit(session); - } - - return new HotRodGlobalLockProvider(session, locksCache, defaultTimeoutMilliseconds); - } - - private void lazyInit(KeycloakSession session) { - HotRodConnectionProvider hotRodConnectionProvider = session.getProvider(HotRodConnectionProvider.class); - locksCache = hotRodConnectionProvider.getRemoteCache(HOT_ROD_LOCKS_CACHE); - } - - @Override - public void init(Config.Scope config) { - defaultTimeoutMilliseconds = config.getLong("defaultTimeoutMilliseconds", 5000L); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodLocksUtils.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodLocksUtils.java deleted file mode 100644 index 91f22a98599..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/locking/HotRodLocksUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.locking; - -import org.infinispan.client.hotrod.Flag; -import org.infinispan.client.hotrod.RemoteCache; -import org.keycloak.common.util.Retry; -import org.keycloak.common.util.Time; -import org.keycloak.models.locking.LockAcquiringTimeoutException; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.time.Duration; -import java.time.Instant; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicReference; - -public class HotRodLocksUtils { - - public static final String SEPARATOR = ";"; - - /** - * Repeatedly attempts to put an entry with the key {@code lockName} - * to the {@code locksCache}. Succeeds only if there is no entry with - * the same key already. - *

- * The value of created entry is equal to instance identifier. It is - * possible to make the method succeed even if the value already exists - * with the same instance identifier. This behaviour is enabled using - * {@code isReentrant} switch. - *

- * Execution of this method is time bounded, if this method does not - * succeed within {@code timeoutMilliseconds} it gives up and returns - * false. - *

- * There is a pause after each unsuccessful attempt equal to - * {@code repeatInterval} milliseconds - * - * @param locksCache Cache that will be used for putting the value - * @param lockName Name of the entry - * @param timeout duration to wait until the lock is acquired - * @param repeatInterval Number of milliseconds to wait after each - * unsuccessful attempt - * @param isReentrant if this is set to true, the method succeeds also when the value for given key is - * equal to the instance identifier - * @throws LockAcquiringTimeoutException the key {@code lockName} was NOT put into the {@code map} - * within time boundaries - * @throws IllegalStateException when a {@code lock} value found in the storage has wrong format. It is expected - * the lock value has the following format {@code 'timeAcquired;keycloakInstanceIdentifier'} - */ - public static void repeatPutIfAbsent(RemoteCache locksCache, String lockName, Duration timeout, int repeatInterval, boolean isReentrant) throws LockAcquiringTimeoutException { - final AtomicReference currentOwnerRef = new AtomicReference<>(null); - try { - Retry.executeWithBackoff(i -> { - String curr = locksCache.withFlags(Flag.FORCE_RETURN_VALUE).putIfAbsent(lockName, Time.currentTimeMillis() + SEPARATOR + getKeycloakInstanceIdentifier()); - currentOwnerRef.set(curr); - if (curr != null) { - if (!isReentrant || !curr.endsWith(SEPARATOR + getKeycloakInstanceIdentifier())) { - throw new AssertionError("Acquiring lock in iteration " + i + " was not successful"); - } - } - }, timeout, repeatInterval); - } catch (AssertionError ex) { - String currentOwner = currentOwnerRef.get(); - String[] split = currentOwner == null ? null : currentOwner.split(SEPARATOR, 2); - if (currentOwner == null || split.length != 2) throw new IllegalStateException("Bad lock value format found in storage for lock " + lockName + ". " + - "It is expected the format to be 'timeAcquired;keycloakInstanceIdentifier' but was " + currentOwner); - throw new LockAcquiringTimeoutException(lockName, split[1], Instant.ofEpochMilli(Long.parseLong(split[0]))); - } - } - - private static String getKeycloakInstanceIdentifier() { - long pid = ProcessHandle.current().pid(); - String hostname; - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - hostname = "unknown-host"; - } - - String threadName = Thread.currentThread().getName(); - return threadName + "#" + pid + "@" + hostname; - } - - /** - * Removes the entry with key {@code lockName} from map if the value - * of the entry is equal to this node's identifier - * - * @param map Map that will be used for removing - * @param lockName Name of the entry - * @return true if the entry was removed, false otherwise - */ - public static boolean removeWithInstanceIdentifier(ConcurrentMap map, String lockName) { - String value = map.get(lockName); - if (value != null && value.endsWith(getKeycloakInstanceIdentifier())) { - map.remove(lockName); - return true; - } else { - return false; - } - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java deleted file mode 100644 index d2818939d04..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/loginFailure/HotRodUserLoginFailureEntity.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.loginFailure; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity", - inherits = "org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity.AbstractHotRodUserLoginFailureEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.UserLoginFailureModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodUserLoginFailureEntity.VERSION) -public class HotRodUserLoginFailureEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodUserLoginFailureEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE) - public interface HotRodUserLoginFailureEntitySchema extends GeneratedSchema { - HotRodUserLoginFailureEntitySchema INSTANCE = new HotRodUserLoginFailureEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Basic(sortable = true) - @ProtoField(number = 4) - public String userId; - - @ProtoField(number = 5) - public Long failedLoginNotBefore; - - @ProtoField(number = 6) - public Integer numFailures; - - @ProtoField(number = 7) - public Long lastFailure; - - @ProtoField(number = 8) - public String lastIPFailure; - - public static abstract class AbstractHotRodUserLoginFailureEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapUserLoginFailureEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodUserLoginFailureEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void clearFailures() { - HotRodUserLoginFailureEntity entity = getHotRodEntity(); - entity.updated |= getFailedLoginNotBefore() != null || getNumFailures() != null || getLastFailure() != null || getLastIPFailure() != null; - setFailedLoginNotBefore(null); - setNumFailures(null); - setLastFailure(null); - setLastIPFailure(null); - } - } - - @Override - public boolean equals(Object o) { - return HotRodUserLoginFailureEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodUserLoginFailureEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/HotRodRealmEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/HotRodRealmEntity.java deleted file mode 100644 index 5c62db94d23..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/HotRodRealmEntity.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.realm; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapComponentEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationExecutionEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationExecutionEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationFlowEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationFlowEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticatorConfigEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticatorConfigEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodClientInitialAccessEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodClientInitialAccessEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodComponentEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodComponentEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderMapperEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderMapperEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodLocalizationTexts; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodOTPPolicyEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntityDelegate; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredentialEntity; -import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntity; - -import java.util.Collections; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.MapRealmEntity", - inherits = "org.keycloak.models.map.storage.hotRod.realm.HotRodRealmEntity.AbstractHotRodRealmEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.RealmModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodRealmEntity.VERSION) -public class HotRodRealmEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodAuthenticationExecutionEntity.class, - HotRodAuthenticationFlowEntity.class, - HotRodAuthenticatorConfigEntity.class, - HotRodClientInitialAccessEntity.class, - HotRodComponentEntity.class, - HotRodIdentityProviderEntity.class, - HotRodIdentityProviderMapperEntity.class, - HotRodLocalizationTexts.class, - HotRodOTPPolicyEntity.class, - HotRodRequiredActionProviderEntity.class, - HotRodRequiredCredentialEntity.class, - HotRodWebAuthnPolicyEntity.class, - HotRodRealmEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodRealmEntitySchema extends GeneratedSchema { - HotRodRealmEntitySchema INSTANCE = new HotRodRealmEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String name; - - @ProtoField(number = 4) - public Boolean adminEventsDetailsEnabled; - @ProtoField(number = 5) - public Boolean adminEventsEnabled; - @ProtoField(number = 6) - public Boolean allowUserManagedAccess; - @ProtoField(number = 7) - public Boolean duplicateEmailsAllowed; - @ProtoField(number = 8) - public Boolean editUsernameAllowed; - @ProtoField(number = 9) - public Boolean enabled; - @ProtoField(number = 10) - public Boolean eventsEnabled; - @ProtoField(number = 11) - public Boolean internationalizationEnabled; - @ProtoField(number = 12) - public Boolean loginWithEmailAllowed; - @ProtoField(number = 13) - public Boolean offlineSessionMaxLifespanEnabled; - @ProtoField(number = 14) - public Boolean registrationAllowed; - @ProtoField(number = 15) - public Boolean registrationEmailAsUsername; - @ProtoField(number = 16) - public Boolean rememberMe; - @ProtoField(number = 17) - public Boolean resetPasswordAllowed; - @ProtoField(number = 18) - public Boolean revokeRefreshToken; - @ProtoField(number = 19) - public Boolean verifyEmail; - @ProtoField(number = 20) - public Integer accessCodeLifespan; - @ProtoField(number = 21) - public Integer accessCodeLifespanLogin; - @ProtoField(number = 22) - public Integer accessCodeLifespanUserAction; - @ProtoField(number = 23) - public Integer accessTokenLifespan; - @ProtoField(number = 24) - public Integer accessTokenLifespanForImplicitFlow; - @ProtoField(number = 25) - public Integer actionTokenGeneratedByAdminLifespan; - @ProtoField(number = 26) - public Integer clientOfflineSessionIdleTimeout; - @ProtoField(number = 27) - public Integer clientOfflineSessionMaxLifespan; - @ProtoField(number = 28) - public Integer clientSessionIdleTimeout; - @ProtoField(number = 29) - public Integer clientSessionMaxLifespan; - @ProtoField(number = 30) - public Long notBefore; - @ProtoField(number = 31) - public Integer offlineSessionIdleTimeout; - @ProtoField(number = 32) - public Integer offlineSessionMaxLifespan; - @ProtoField(number = 33) - public Integer refreshTokenMaxReuse; - @ProtoField(number = 34) - public Integer ssoSessionIdleTimeout; - @ProtoField(number = 35) - public Integer ssoSessionIdleTimeoutRememberMe; - @ProtoField(number = 36) - public Integer ssoSessionMaxLifespan; - @ProtoField(number = 37) - public Integer ssoSessionMaxLifespanRememberMe; - @ProtoField(number = 38) - public Long eventsExpiration; - @ProtoField(number = 39) - public HotRodOTPPolicyEntity oTPPolicy; - @ProtoField(number = 40) - public HotRodWebAuthnPolicyEntity webAuthnPolicy; - @ProtoField(number = 41) - public HotRodWebAuthnPolicyEntity webAuthnPolicyPasswordless; - @ProtoField(number = 42) - public String accountTheme; - @ProtoField(number = 43) - public String adminTheme; - @ProtoField(number = 44) - public String browserFlow; - @ProtoField(number = 45) - public String clientAuthenticationFlow; - @ProtoField(number = 46) - public String defaultLocale; - @ProtoField(number = 47) - public String defaultRoleId; - @ProtoField(number = 48) - public String directGrantFlow; - @ProtoField(number = 49) - public String displayName; - @ProtoField(number = 50) - public String displayNameHtml; - @ProtoField(number = 51) - public String dockerAuthenticationFlow; - @ProtoField(number = 52) - public String emailTheme; - @ProtoField(number = 53) - public String loginTheme; - @ProtoField(number = 54) - public String masterAdminClient; - @ProtoField(number = 55) - public String passwordPolicy; - @ProtoField(number = 56) - public String registrationFlow; - @ProtoField(number = 57) - public String resetCredentialsFlow; - @ProtoField(number = 58) - public String sslRequired; - @ProtoField(number = 59) - public Set attributes; - @ProtoField(number = 60) - public Set localizationTexts; - @ProtoField(number = 61) - public Set> browserSecurityHeaders; - @ProtoField(number = 62) - public Set> smtpConfig; - @ProtoField(number = 63) - public Set authenticationExecutions; - @ProtoField(number = 64) - public Set authenticationFlows; - @ProtoField(number = 65) - public Set authenticatorConfigs; - - @Basic(sortable = true) - @ProtoField(number = 66) - public Set clientInitialAccesses; - - @Basic(sortable = true) - @ProtoField(number = 67) - public Set components; - - @ProtoField(number = 68) - public Set identityProviders; - @ProtoField(number = 69) - public Set identityProviderMappers; - @ProtoField(number = 70) - public Set requiredActionProviders; - @ProtoField(number = 71) - public Set requiredCredentials; - @ProtoField(number = 72) - public Set defaultClientScopeIds; - @ProtoField(number = 73) - public Set defaultGroupIds; - @ProtoField(number = 74) - public Set enabledEventTypes; - @ProtoField(number = 75) - public Set eventsListeners; - @ProtoField(number = 76) - public Set optionalClientScopeIds; - @ProtoField(number = 77) - public Set supportedLocales; - - - public static abstract class AbstractHotRodRealmEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapRealmEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodRealmEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public boolean isUpdated() { - return getHotRodEntity().updated - || Optional.ofNullable(getAuthenticationExecutions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationExecutionEntity::isUpdated) - || Optional.ofNullable(getAuthenticationFlows()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationFlowEntity::isUpdated) - || Optional.ofNullable(getAuthenticatorConfigs()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticatorConfigEntity::isUpdated) - || Optional.ofNullable(getClientInitialAccesses()).orElseGet(Collections::emptySet).stream().anyMatch(MapClientInitialAccessEntity::isUpdated) - || Optional.ofNullable(getComponents()).orElseGet(Collections::emptySet).stream().anyMatch(MapComponentEntity::isUpdated) - || Optional.ofNullable(getIdentityProviders()).orElseGet(Collections::emptySet).stream().anyMatch(MapIdentityProviderEntity::isUpdated) - || Optional.ofNullable(getIdentityProviderMappers()).orElseGet(Collections::emptySet).stream().anyMatch(MapIdentityProviderMapperEntity::isUpdated) - || Optional.ofNullable(getRequiredActionProviders()).orElseGet(Collections::emptySet).stream().anyMatch(MapRequiredActionProviderEntity::isUpdated) - || Optional.ofNullable(getRequiredCredentials()).orElseGet(Collections::emptySet).stream().anyMatch(MapRequiredCredentialEntity::isUpdated) - || Optional.ofNullable(getOTPPolicy()).map(MapOTPPolicyEntity::isUpdated).orElse(false) - || Optional.ofNullable(getWebAuthnPolicy()).map(MapWebAuthnPolicyEntity::isUpdated).orElse(false) - || Optional.ofNullable(getWebAuthnPolicyPasswordless()).map(MapWebAuthnPolicyEntity::isUpdated).orElse(false); - } - - @Override - public void clearUpdatedFlag() { - getHotRodEntity().updated = false; - Optional.ofNullable(getAuthenticationExecutions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getAuthenticationFlows()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getAuthenticatorConfigs()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getClientInitialAccesses()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getComponents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getIdentityProviders()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getIdentityProviderMappers()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getRequiredActionProviders()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getRequiredCredentials()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getOTPPolicy()).ifPresent(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getWebAuthnPolicy()).ifPresent(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getWebAuthnPolicyPasswordless()).ifPresent(UpdatableEntity::clearUpdatedFlag); - } - - @Override - public boolean hasClientInitialAccess() { - Set cias = getClientInitialAccesses(); - return cias != null && !cias.isEmpty(); - } - - @Override - public void removeExpiredClientInitialAccesses() { - Set cias = getClientInitialAccesses(); - if (cias != null) - cias.stream() - .filter(this::checkIfExpired) - .map(MapClientInitialAccessEntity::getId) - .collect(Collectors.toSet()) - .forEach(this::removeClientInitialAccess); - } - - private boolean checkIfExpired(MapClientInitialAccessEntity cia) { - return cia.getRemainingCount() < 1 || isExpired(cia, true); - } - } - @Override - public boolean equals(Object o) { - return HotRodRealmEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodRealmEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticationExecutionEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticationExecutionEntity.java deleted file mode 100644 index fd0a541c223..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticationExecutionEntity.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity" -) -public class HotRodAuthenticationExecutionEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public Boolean autheticatorFlow; - @ProtoField(number = 3) - public Integer priority; - @ProtoField(number = 4) - public Integer requirement; - @ProtoField(number = 5) - public String authenticator; - @ProtoField(number = 6) - public String authenticatorConfig; - @ProtoField(number = 7) - public String flowId; - @ProtoField(number = 8) - public String parentFlowId; - @Override - public boolean equals(Object o) { - return HotRodAuthenticationExecutionEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAuthenticationExecutionEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticationFlowEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticationFlowEntity.java deleted file mode 100644 index 72be72c913e..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticationFlowEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity" -) -public class HotRodAuthenticationFlowEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public Boolean builtIn; - @ProtoField(number = 3) - public Boolean topLevel; - @ProtoField(number = 4) - public String alias; - @ProtoField(number = 5) - public String description; - @ProtoField(number = 6) - public String providerId; - @Override - public boolean equals(Object o) { - return HotRodAuthenticationFlowEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAuthenticationFlowEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticatorConfigEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticatorConfigEntity.java deleted file mode 100644 index edca356220d..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodAuthenticatorConfigEntity.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity" -) -public class HotRodAuthenticatorConfigEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public String alias; - @ProtoField(number = 3) - public Set> config; - @Override - public boolean equals(Object o) { - return HotRodAuthenticatorConfigEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAuthenticatorConfigEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodClientInitialAccessEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodClientInitialAccessEntity.java deleted file mode 100644 index 1f3f166e10e..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodClientInitialAccessEntity.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity" -) -public class HotRodClientInitialAccessEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public Integer count; - @ProtoField(number = 3) - public Long expiration; - @ProtoField(number = 4) - public Integer remainingCount; - @ProtoField(number = 5) - public Long timestamp; - @Override - public boolean equals(Object o) { - return HotRodClientInitialAccessEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodClientInitialAccessEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodComponentEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodComponentEntity.java deleted file mode 100644 index cb54fc5f017..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodComponentEntity.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapComponentEntity" -) -@Indexed -public class HotRodComponentEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public String name; - @ProtoField(number = 3) - public String parentId; - @ProtoField(number = 4) - public String providerId; - - @Basic(sortable = true) - @ProtoField(number = 5) - public String providerType; - - @ProtoField(number = 6) - public String subType; - @ProtoField(number = 7) - public Set config; - @Override - public boolean equals(Object o) { - return HotRodComponentEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodComponentEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodIdentityProviderEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodIdentityProviderEntity.java deleted file mode 100644 index e219cc315ca..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodIdentityProviderEntity.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapIdentityProviderEntity" -) -public class HotRodIdentityProviderEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public Boolean addReadTokenRoleOnCreate; - @ProtoField(number = 3) - public Boolean authenticateByDefault; - @ProtoField(number = 4) - public Boolean enabled; - @ProtoField(number = 5) - public Boolean linkOnly; - @ProtoField(number = 6) - public Boolean storeToken; - @ProtoField(number = 7) - public Boolean trustEmail; - @ProtoField(number = 8) - public String alias; - @ProtoField(number = 9) - public String displayName; - @ProtoField(number = 10) - public String firstBrokerLoginFlowId; - @ProtoField(number = 11) - public String postBrokerLoginFlowId; - @ProtoField(number = 12) - public String providerId; - @ProtoField(number = 13) - public Set> config; - @Override - public boolean equals(Object o) { - return HotRodIdentityProviderEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodIdentityProviderEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodIdentityProviderMapperEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodIdentityProviderMapperEntity.java deleted file mode 100644 index 39a240fefe2..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodIdentityProviderMapperEntity.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity" -) -public class HotRodIdentityProviderMapperEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public String name; - @ProtoField(number = 3) - public String identityProviderAlias; - @ProtoField(number = 4) - public String identityProviderMapper; - @ProtoField(number = 5) - public Set> config; - @Override - public boolean equals(Object o) { - return HotRodIdentityProviderMapperEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodIdentityProviderMapperEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodLocalizationTexts.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodLocalizationTexts.java deleted file mode 100644 index b8814bae7f9..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodLocalizationTexts.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; - -import java.util.Objects; -import java.util.Set; - -public class HotRodLocalizationTexts { - @ProtoField(number = 1) - public String locale; - - @ProtoField(number = 2) - public Set> values; - - public String getLocale() { - return locale; - } - - public void setLocale(String locale) { - this.locale = locale; - } - - public Set> getValues() { - return values; - } - - public void setValues(Set> values) { - this.values = values; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HotRodLocalizationTexts that = (HotRodLocalizationTexts) o; - return Objects.equals(locale, that.locale) && Objects.equals(values, that.values); - } - - @Override - public int hashCode() { - return Objects.hash(locale, values); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodOTPPolicyEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodOTPPolicyEntity.java deleted file mode 100644 index 1fd0bc72d50..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodOTPPolicyEntity.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapOTPPolicyEntity" -) -public class HotRodOTPPolicyEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public Integer otpPolicyDigits; - @ProtoField(number = 2) - public Integer otpPolicyInitialCounter; - @ProtoField(number = 3) - public Integer otpPolicyLookAheadWindow; - @ProtoField(number = 4) - public Integer otpPolicyPeriod; - @ProtoField(number = 5) - public String otpPolicyAlgorithm; - @ProtoField(number = 6) - public String otpPolicyType; - @ProtoField(number = 7) - public Boolean otpPolicyCodeReusable; - @Override - public boolean equals(Object o) { - return HotRodOTPPolicyEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodOTPPolicyEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodRequiredActionProviderEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodRequiredActionProviderEntity.java deleted file mode 100644 index f8c9b42799d..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodRequiredActionProviderEntity.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity" -) -public class HotRodRequiredActionProviderEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public String id; - @ProtoField(number = 2) - public String name; - @ProtoField(number = 3) - public Boolean defaultAction; - @ProtoField(number = 4) - public Boolean enabled; - @ProtoField(number = 5) - public Integer priority; - @ProtoField(number = 6) - public String alias; - @ProtoField(number = 7) - public String providerId; - @ProtoField(number = 8) - public Set> config; - @Override - public boolean equals(Object o) { - return HotRodRequiredActionProviderEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodRequiredActionProviderEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodRequiredCredentialEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodRequiredCredentialEntity.java deleted file mode 100644 index 9dadc36e141..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodRequiredCredentialEntity.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity" -) -public class HotRodRequiredCredentialEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public Boolean input; - @ProtoField(number = 2) - public Boolean secret; - @ProtoField(number = 3) - public String formLabel; - @ProtoField(number = 4) - public String type; - @Override - public boolean equals(Object o) { - return HotRodRequiredCredentialEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodRequiredCredentialEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodWebAuthnPolicyEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodWebAuthnPolicyEntity.java deleted file mode 100644 index fd656c7ba50..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/realm/entity/HotRodWebAuthnPolicyEntity.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.keycloak.models.map.storage.hotRod.realm.entity; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -import java.util.List; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity" -) -public class HotRodWebAuthnPolicyEntity extends AbstractHotRodEntity { - @ProtoField(number = 1) - public Boolean avoidSameAuthenticatorRegister; - @ProtoField(number = 2) - public Integer createTimeout; - @ProtoField(number = 3) - public String attestationConveyancePreference; - @ProtoField(number = 4) - public String authenticatorAttachment; - @ProtoField(number = 5) - public String requireResidentKey; - @ProtoField(number = 6) - public String rpEntityName; - @ProtoField(number = 7) - public String rpId; - @ProtoField(number = 8) - public String userVerificationRequirement; - @ProtoField(number = 9) - public List acceptableAaguids; - @ProtoField(number = 10) - public List signatureAlgorithms; - @ProtoField(number = 11) - public List extraOrigins; - @Override - public boolean equals(Object o) { - return HotRodWebAuthnPolicyEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodWebAuthnPolicyEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/role/HotRodRoleEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/role/HotRodRoleEntity.java deleted file mode 100644 index 97014ac2a6c..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/role/HotRodRoleEntity.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.role; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Objects; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.role.MapRoleEntity", - inherits = "org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity.AbstractHotRodRoleEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.RoleModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodRoleEntity.VERSION) -public class HotRodRoleEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodRoleEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodRoleEntitySchema extends GeneratedSchema { - HotRodRoleEntitySchema INSTANCE = new HotRodRoleEntitySchemaImpl(); - } - - - public static abstract class AbstractHotRodRoleEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapRoleEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodRoleEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setClientId(String clientId) { - HotRodRoleEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.clientId, clientId); - entity.clientId = clientId; - } - - @Override - public void setName(String name) { - HotRodRoleEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.name, name); - entity.name = name; - } - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 4) - public String name; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 5) - public String description; - - @Basic(sortable = true) - @ProtoField(number = 7) - public String clientId; - - @Basic(sortable = true) - @ProtoField(number = 8) - public Set compositeRoles; - - @ProtoField(number = 9) - public Set attributes; - - @Override - public boolean equals(Object o) { - return HotRodRoleEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodRoleEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/singleUseObject/HotRodSingleUseObjectEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/singleUseObject/HotRodSingleUseObjectEntity.java deleted file mode 100644 index 9d62acc2f79..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/singleUseObject/HotRodSingleUseObjectEntity.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.singleUseObject; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity", - inherits = "org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity.AbstractHotRodSingleUseObjectEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.SingleUseObjectValueModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodSingleUseObjectEntity.VERSION) -public class HotRodSingleUseObjectEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodSingleUseObjectEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodSingleUseObjectEntitySchema extends GeneratedSchema { - HotRodSingleUseObjectEntitySchema INSTANCE = new HotRodSingleUseObjectEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @ProtoField(number = 3) - public String objectKey; - - @ProtoField(number = 7) - public Long expiration; - - @ProtoField(number = 8) - public Set> notes; - - public static abstract class AbstractHotRodSingleUseObjectEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapSingleUseObjectEntity { - - @Override - public String getId() { - HotRodSingleUseObjectEntity hotRodEntity = getHotRodEntity(); - return hotRodEntity != null ? hotRodEntity.id : null; - } - - @Override - public void setId(String id) { - HotRodSingleUseObjectEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodSingleUseObjectEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodSingleUseObjectEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/AllAreasHotRodStoresWrapper.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/AllAreasHotRodStoresWrapper.java deleted file mode 100644 index 6469789b4f1..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/AllAreasHotRodStoresWrapper.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.transaction; - -import org.keycloak.models.AbstractKeycloakTransaction; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; - -/** - * This wrapper encapsulates stores from all areas. This is needed because we need to control when the changes - * from each area are applied to make sure it is performed before the HotRod client provided transaction is committed. - */ -public class AllAreasHotRodStoresWrapper extends AbstractKeycloakTransaction { - - private final Map, ConcurrentHashMapStorage> MapKeycloakStoresMap = new ConcurrentHashMap<>(); - - public ConcurrentHashMapStorage getOrCreateStoreForModel(Class modelType, Supplier> supplier) { - ConcurrentHashMapStorage store = MapKeycloakStoresMap.computeIfAbsent(modelType, t -> supplier.get()); - if (!store.isActive()) { - store.begin(); - } - - return store; - } - - @Override - protected void commitImpl() { - MapKeycloakStoresMap.values().forEach(ConcurrentHashMapStorage::commit); - } - - @Override - protected void rollbackImpl() { - MapKeycloakStoresMap.values().forEach(ConcurrentHashMapStorage::rollback); - } - - @Override - public void setRollbackOnly() { - super.setRollbackOnly(); - MapKeycloakStoresMap.values().forEach(ConcurrentHashMapStorage::setRollbackOnly); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/HotRodRemoteTransactionWrapper.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/HotRodRemoteTransactionWrapper.java deleted file mode 100644 index f2beff64e27..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/HotRodRemoteTransactionWrapper.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.transaction; - -import org.keycloak.models.KeycloakTransaction; - -import jakarta.transaction.HeuristicMixedException; -import jakarta.transaction.HeuristicRollbackException; -import jakarta.transaction.NotSupportedException; -import jakarta.transaction.RollbackException; -import jakarta.transaction.Status; -import jakarta.transaction.SystemException; -import jakarta.transaction.TransactionManager; - -/** - * When no JTA transaction is present in the runtime this wrapper is used - * to enlist HotRod client provided transaction to our - * {@link KeycloakTransactionManager}. If JTA transaction is present this should - * not be used. - */ -public class HotRodRemoteTransactionWrapper implements KeycloakTransaction { - - private final TransactionManager transactionManager; - - public HotRodRemoteTransactionWrapper(TransactionManager transactionManager) { - this.transactionManager = transactionManager; - } - - @Override - public void begin() { - try { - if (transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION) { - transactionManager.begin(); - } - } catch (NotSupportedException | SystemException e) { - throw new RuntimeException(e); - } - } - - @Override - public void commit() { - try { - if (transactionManager.getStatus() == Status.STATUS_ACTIVE) { - transactionManager.commit(); - } - } catch (HeuristicRollbackException | SystemException | HeuristicMixedException | RollbackException e) { - throw new RuntimeException(e); - } - } - - @Override - public void rollback() { - try { - if (transactionManager.getStatus() == Status.STATUS_ACTIVE) { - transactionManager.rollback(); - } - } catch (SystemException e) { - throw new RuntimeException(e); - } - } - - @Override - public void setRollbackOnly() { - try { - if (transactionManager.getStatus() == Status.STATUS_ACTIVE) { - transactionManager.setRollbackOnly(); - } - } catch (SystemException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean getRollbackOnly() { - try { - return transactionManager.getStatus() == Status.STATUS_MARKED_ROLLBACK; - } catch (SystemException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean isActive() { - try { - return transactionManager.getStatus() == Status.STATUS_ACTIVE; - } catch (SystemException e) { - throw new RuntimeException(e); - } - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/HotRodTransactionManagerLookup.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/HotRodTransactionManagerLookup.java deleted file mode 100644 index f28c7ceb202..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/transaction/HotRodTransactionManagerLookup.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.transaction; - -import org.infinispan.client.hotrod.transaction.manager.RemoteTransactionManager; -import org.infinispan.commons.tx.lookup.TransactionManagerLookup; -import org.keycloak.models.KeycloakSession; -import org.keycloak.transaction.JtaTransactionManagerLookup; - -import jakarta.transaction.TransactionManager; - -/** - * HotRod client provides its own {@link org.infinispan.client.hotrod.transaction.lookup.GenericTransactionManagerLookup} - * that is able to locate variety of JTA transaction implementation present - * in the runtime. We need to make sure we use JTA only when it is detected - * by other parts of Keycloak (such as {@link org.keycloak.models.KeycloakTransactionManager}), - * therefore we implemented this custom TransactionManagerLookup that locates - * JTA transaction using {@link JtaTransactionManagerLookup} provider - * - */ -public class HotRodTransactionManagerLookup implements TransactionManagerLookup { - - private final TransactionManager transactionManager; - - public HotRodTransactionManagerLookup(KeycloakSession session) { - JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class); - TransactionManager txManager = jtaLookup != null ? jtaLookup.getTransactionManager() : null; - transactionManager = txManager != null ? txManager : RemoteTransactionManager.getInstance(); - } - - @Override - public TransactionManager getTransactionManager() throws Exception { - return transactionManager; - } - -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserConsentEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserConsentEntity.java deleted file mode 100644 index 750e1a90a4b..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserConsentEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.user; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -import java.util.Set; - -@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserConsentEntity") -@Indexed -public class HotRodUserConsentEntity extends AbstractHotRodEntity { - @Basic(sortable = true) - @ProtoField(number = 1) - public String clientId; - - @Basic(sortable = true) - @ProtoField(number = 2) - public Set grantedClientScopesIds; - - @ProtoField(number = 3) - public Long createdDate; - - @ProtoField(number = 4) - public Long lastUpdatedDate; - - @Override - public boolean equals(Object o) { - return HotRodUserConsentEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodUserConsentEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserCredentialEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserCredentialEntity.java deleted file mode 100644 index fb029bf96e3..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserCredentialEntity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.user; - -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserCredentialEntity") -public class HotRodUserCredentialEntity extends AbstractHotRodEntity { - - @ProtoField(number = 1) - public String id; - - @ProtoField(number = 2) - public String type; - - @ProtoField(number = 3) - public String userLabel; - - @ProtoField(number = 4) - public Long createdDate; - - @ProtoField(number = 5) - public String secretData; - - @ProtoField(number = 6) - public String credentialData; - - @ProtoField(number = 7) - public Integer priority; - - @Override - public boolean equals(Object o) { - return HotRodUserCredentialEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodUserCredentialEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserEntity.java deleted file mode 100644 index 22459bd7e7a..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserEntity.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.user; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.api.annotations.indexing.Keyword; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.jboss.logging.Logger; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; -import org.keycloak.models.map.user.MapUserConsentEntity; -import org.keycloak.models.map.user.MapUserCredentialEntity; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.user.MapUserFederatedIdentityEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.user.MapUserEntity", - inherits = "org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity.AbstractHotRodUserEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.UserModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodUserEntity.VERSION) -public class HotRodUserEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodUserEntity.class, - HotRodUserConsentEntity.class, - HotRodUserCredentialEntity.class, - HotRodUserFederatedIdentityEntity.class}, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodUserEntitySchema extends GeneratedSchema { - HotRodUserEntitySchema INSTANCE = new HotRodUserEntitySchemaImpl(); - } - - @IgnoreForEntityImplementationGenerator - private static final Logger LOG = Logger.getLogger(HotRodUserEntity.class); - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Basic(sortable = true) - @ProtoField(number = 4) - public String username; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 5) - public String usernameLowercase; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 6) - public String firstName; - - @ProtoField(number = 7) - public Long createdTimestamp; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 8) - public String lastName; - - @Keyword(sortable = true, normalizer = "lowercase") - @ProtoField(number = 9) - public String email; - - /** - * TODO: Workaround for ISPN-8584 - * - * This index shouldn't be there as majority of object will be enabled == true - * - * When this index is missing Ickle queries like following: - * FROM kc.HotRodUserEntity c WHERE (c.realmId = "admin-client-test" AND c.enabled = true AND c.email : "user*") - * fail with: - * Error: {"error":{"message":"Error executing search","cause":"Unexpected condition type (FullTextTermExpr): PROP(email):'user*'"}} - * - * In other words it is not possible to combine searching for Analyzed field and non-indexed field in one Ickle query - */ - @Basic(sortable = true) - @ProtoField(number = 10) - public Boolean enabled; - - /** - * TODO: Workaround for ISPN-8584 - * - * When this index is missing Ickle queries like following: - * FROM kc.HotRodUserEntity c WHERE (c.realmId = "admin-client-test" AND c.enabled = true AND c.email : "user*") - * fail with: - * Error: {"error":{"message":"Error executing search","cause":"Unexpected condition type (FullTextTermExpr): PROP(email):'user*'"}} - * - * In other words it is not possible to combine searching for Analyzed field and non-indexed field in one Ickle query - */ - @Basic(sortable = true) - @ProtoField(number = 11) - public Boolean emailVerified; - - // This is necessary to be able to dynamically switch unique email constraints on and off in the realm settings - @ProtoField(number = 12) - public String emailConstraint; - - @Basic(sortable = true) - @ProtoField(number = 13) - public Set attributes; - - @ProtoField(number = 14) - public Set requiredActions; - - @ProtoField(number = 15) - public List credentials; - - @Basic(sortable = true) - @ProtoField(number = 16) - public Set federatedIdentities; - - @Basic(sortable = true) - @ProtoField(number = 17) - public Set userConsents; - - @Basic(sortable = true) - @ProtoField(number = 18) - public Set groupsMembership = new HashSet<>(); - - @Basic(sortable = true) - @ProtoField(number = 19) - public Set rolesMembership = new HashSet<>(); - - @Basic(sortable = true) - @ProtoField(number = 20) - public String federationLink; - - @Basic(sortable = true) - @ProtoField(number = 21) - public String serviceAccountClientLink; - - @ProtoField(number = 22) - public Long notBefore; - - public static abstract class AbstractHotRodUserEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapUserEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodUserEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setEmail(String email, boolean duplicateEmailsAllowed) { - this.setEmail(email); - this.setEmailConstraint(email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email); - } - - @Override - public void setUsername(String username) { - HotRodUserEntity entity = getHotRodEntity(); - entity.updated |= ! Objects.equals(entity.username, username); - entity.username = username; - entity.usernameLowercase = username == null ? null : username.toLowerCase(); - } - - @Override - public boolean isUpdated() { - return getHotRodEntity().updated - || Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserConsentEntity::isUpdated) - || Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).stream().anyMatch(MapUserCredentialEntity::isUpdated) - || Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserFederatedIdentityEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - getHotRodEntity().updated = false; - Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - } - - @Override - public Boolean moveCredential(String credentialId, String newPreviousCredentialId) { - // 1 - Get all credentials from the entity. - List credentialsList = getHotRodEntity().credentials; - - // 2 - Find indexes of our and newPrevious credential - int ourCredentialIndex = -1; - int newPreviousCredentialIndex = -1; - HotRodUserCredentialEntity ourCredential = null; - int i = 0; - for (HotRodUserCredentialEntity credential : credentialsList) { - if (credentialId.equals(credential.id)) { - ourCredentialIndex = i; - ourCredential = credential; - } else if(newPreviousCredentialId != null && newPreviousCredentialId.equals(credential.id)) { - newPreviousCredentialIndex = i; - } - i++; - } - - if (ourCredentialIndex == -1) { - LOG.warnf("Not found credential with id [%s] of user [%s]", credentialId, getUsername()); - return false; - } - - if (newPreviousCredentialId != null && newPreviousCredentialIndex == -1) { - LOG.warnf("Can't move up credential with id [%s] of user [%s]", credentialId, getUsername()); - return false; - } - - // 3 - Compute index where we move our credential - int toMoveIndex = newPreviousCredentialId==null ? 0 : newPreviousCredentialIndex + 1; - - // 4 - Insert our credential to new position, remove it from the old position - if (toMoveIndex == ourCredentialIndex) return true; - credentialsList.add(toMoveIndex, ourCredential); - int indexToRemove = toMoveIndex < ourCredentialIndex ? ourCredentialIndex + 1 : ourCredentialIndex; - credentialsList.remove(indexToRemove); - - getHotRodEntity().updated = true; - return true; - } - - } - - @Override - public boolean equals(Object o) { - return HotRodUserEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodUserEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserFederatedIdentityEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserFederatedIdentityEntity.java deleted file mode 100644 index 77774ebec14..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/user/HotRodUserFederatedIdentityEntity.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.user; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; - -@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserFederatedIdentityEntity") -@Indexed -public class HotRodUserFederatedIdentityEntity extends AbstractHotRodEntity { - - @Basic(sortable = true) - @ProtoField(number = 1) - public String identityProvider; - - @ProtoField(number = 2) - public String token; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String userId; - - @ProtoField(number = 4) - public String userName; - - @Override - public boolean equals(Object o) { - return HotRodUserFederatedIdentityEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodUserFederatedIdentityEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/AuthenticatedClientSessionReferenceOnlyFieldDelegate.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/AuthenticatedClientSessionReferenceOnlyFieldDelegate.java deleted file mode 100644 index ab642d91f12..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/AuthenticatedClientSessionReferenceOnlyFieldDelegate.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.userSession; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityFields; - -public class AuthenticatedClientSessionReferenceOnlyFieldDelegate implements EntityFieldDelegate { - - private final HotRodAuthenticatedClientSessionEntityReference reference; - - public AuthenticatedClientSessionReferenceOnlyFieldDelegate(HotRodAuthenticatedClientSessionEntityReference reference) { - this.reference = reference; - } - - @Override - public boolean isUpdated() { - return false; - } - - @Override - public > & EntityField> Object get(EF field) { - switch ((MapAuthenticatedClientSessionEntityFields)field) { - case ID: return reference.getClientSessionId(); - case CLIENT_ID: return reference.getClientId(); - } - - return null; - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void set(EF field, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void collectionAdd(EF field, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object collectionRemove(EF field, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapGet(EF field, K key) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void mapPut(EF field, K key, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapRemove(EF field, K key) { - throw new UnsupportedOperationException("Not supported yet."); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntity.java deleted file mode 100644 index a38ab0e1acb..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntity.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.userSession; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodPair; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.storage.SearchableModelField; - -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity", - inherits = "org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntity.AbstractHotRodAuthenticatedClientSessionEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.AuthenticatedClientSessionModel", - cacheName = "org.keycloak.models.map.storage.ModelEntityUtil.getModelName(org.keycloak.models.UserSessionModel.class)" // Use the same cache name as user-sessions -) -@ProtoDoc("schema-version: " + HotRodResourceServerEntity.VERSION) -@Indexed -public class HotRodAuthenticatedClientSessionEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @IgnoreForEntityImplementationGenerator - public static final SearchableModelField ID = new SearchableModelField<>("id", String.class); - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodAuthenticatedClientSessionEntity.class - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodAuthenticatedClientSessionEntitySchema extends GeneratedSchema { - HotRodAuthenticatedClientSessionEntitySchema INSTANCE = new HotRodAuthenticatedClientSessionEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @Basic(projectable = true, sortable = true) - @ProtoField(number = 2) - public String id; - - @ProtoField(number = 3) - public String realmId; - - @ProtoField(number = 4) - public String clientId; - - @ProtoField(number = 5) - public String authMethod; - - @ProtoField(number = 6) - public String redirectUri; - - @ProtoField(number = 7) - public Long timestamp; - - @ProtoField(number = 8) - public Long expiration; - - @ProtoField(number = 9) - public String action; - - @ProtoField(number = 10) - public Set> notes; - - @ProtoField(number = 11) - public String currentRefreshToken; - - @ProtoField(number = 12) - public Integer currentRefreshTokenUseCount; - - @ProtoField(number = 13) - public Boolean offline; - - public static abstract class AbstractHotRodAuthenticatedClientSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapAuthenticatedClientSessionEntity { - @Override - public void setId(String id) { - HotRodAuthenticatedClientSessionEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public void setClientId(String clientId) { - HotRodAuthenticatedClientSessionEntity entity = getHotRodEntity(); - if (entity.clientId != null) throw new IllegalStateException("ClientId cannot be changed"); - entity.clientId = clientId; - entity.updated |= clientId != null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodAuthenticatedClientSessionEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodAuthenticatedClientSessionEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntityDelegateProvider.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntityDelegateProvider.java deleted file mode 100644 index a9c588078dc..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntityDelegateProvider.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.userSession; - -import org.keycloak.models.ModelIllegalStateException; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityFields; - -public abstract class HotRodAuthenticatedClientSessionEntityDelegateProvider implements DelegateProvider { - - private MapAuthenticatedClientSessionEntity fullClientSessionData; - private MapAuthenticatedClientSessionEntity idClientIdReferenceOnly; - - public HotRodAuthenticatedClientSessionEntityDelegateProvider(MapAuthenticatedClientSessionEntity idClientIdReferenceOnly) { - this.idClientIdReferenceOnly = idClientIdReferenceOnly; - } - - @Override - public MapAuthenticatedClientSessionEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (fullClientSessionData != null) return fullClientSessionData; - - if (isRead) { - switch ((MapAuthenticatedClientSessionEntityFields) field) { - case ID: - case CLIENT_ID: - return idClientIdReferenceOnly; - } - } - - fullClientSessionData = loadClientSessionFromDatabase(); - if (fullClientSessionData == null) { - throw new ModelIllegalStateException("Unable to retrieve client session data with id: " + idClientIdReferenceOnly.getId()); - } - - return fullClientSessionData; - } - - public abstract MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase(); -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntityReference.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntityReference.java deleted file mode 100644 index 660ef98aa98..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodAuthenticatedClientSessionEntityReference.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.userSession; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.annotations.ProtoField; - -@Indexed -public class HotRodAuthenticatedClientSessionEntityReference { - - @Basic(sortable = true) - @ProtoField(number = 1) - public String clientId; - - @ProtoField(number = 2) - public String clientSessionId; - - public HotRodAuthenticatedClientSessionEntityReference() {} - - public HotRodAuthenticatedClientSessionEntityReference(String clientId, String clientSessionId) { - this.clientId = clientId; - this.clientSessionId = clientSessionId; - } - - public String getClientId() { - return clientId; - } - - public String getClientSessionId() { - return clientSessionId; - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodUserSessionEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodUserSessionEntity.java deleted file mode 100644 index 0db608431ae..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodUserSessionEntity.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.userSession; - -import org.infinispan.api.annotations.indexing.Basic; -import org.infinispan.api.annotations.indexing.Indexed; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.annotations.ProtoDoc; -import org.infinispan.protostream.annotations.ProtoField; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntity; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity; -import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; -import org.keycloak.models.map.storage.hotRod.common.HotRodStringPair; -import org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils; -import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity; - -import java.util.Collections; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -@GenerateHotRodEntityImplementation( - implementInterface = "org.keycloak.models.map.userSession.MapUserSessionEntity", - inherits = "org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity.AbstractHotRodUserSessionEntityDelegate", - topLevelEntity = true, - modelClass = "org.keycloak.models.UserSessionModel" -) -@Indexed -@ProtoDoc("schema-version: " + HotRodResourceServerEntity.VERSION) -public class HotRodUserSessionEntity extends AbstractHotRodEntity { - - @IgnoreForEntityImplementationGenerator - public static final int VERSION = 1; - - @AutoProtoSchemaBuilder( - includeClasses = { - HotRodUserSessionEntity.class, - HotRodAuthenticatedClientSessionEntityReference.class, - }, - schemaFilePath = "proto/", - schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE, - dependsOn = {CommonPrimitivesProtoSchemaInitializer.class} - ) - public interface HotRodUserSessionEntitySchema extends GeneratedSchema { - HotRodUserSessionEntitySchema INSTANCE = new HotRodUserSessionEntitySchemaImpl(); - } - - @Basic(projectable = true) - @ProtoField(number = 1) - public Integer entityVersion = VERSION; - - @ProtoField(number = 2) - public String id; - - @Basic(sortable = true) - @ProtoField(number = 3) - public String realmId; - - @Basic(sortable = true) - @ProtoField(number = 4) - public String userId; - - @Basic(sortable = true) - @ProtoField(number = 5) - public String brokerSessionId; - - @Basic(sortable = true) - @ProtoField(number = 6) - public String brokerUserId; - - @ProtoField(number = 7) - public String loginUsername; - - @ProtoField(number = 8) - public String ipAddress; - - @ProtoField(number = 9) - public String authMethod; - - @ProtoField(number = 10) - public Boolean rememberMe; - - @ProtoField(number = 11) - public Long timestamp; - - @Basic(sortable = true) - @ProtoField(number = 12) - public Long lastSessionRefresh; - - @ProtoField(number = 13) - public Long expiration; - - @Basic(sortable = true) - @ProtoField(number = 14) - public Set notes; - - @ProtoField(number = 15) - public Integer state; - - @Basic(sortable = true) - @ProtoField(number = 16) - public Set authenticatedClientSessions; - - @Basic(sortable = true) - @ProtoField(number = 17) - public Boolean offline; - - public static abstract class AbstractHotRodUserSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapUserSessionEntity { - - @Override - public String getId() { - return getHotRodEntity().id; - } - - @Override - public void setId(String id) { - HotRodUserSessionEntity entity = getHotRodEntity(); - if (entity.id != null) throw new IllegalStateException("Id cannot be changed"); - entity.id = id; - entity.updated |= id != null; - } - - @Override - public UserSessionModel.SessionPersistenceState getPersistenceState() { - return UserSessionModel.SessionPersistenceState.PERSISTENT; - } - - @Override - public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) { - if (persistenceState != null && UserSessionModel.SessionPersistenceState.PERSISTENT != persistenceState) { - throw new IllegalArgumentException("Transient session should not be stored in the HotRod."); - } - } - - @Override - public boolean isUpdated() { - return getHotRodEntity().updated - || Optional.ofNullable(getAuthenticatedClientSessions()).orElseGet(Collections::emptySet).stream().anyMatch(UpdatableEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - getHotRodEntity().updated = false; - Optional.ofNullable(getAuthenticatedClientSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - } - - @Override - public void clearAuthenticatedClientSessions() { - HotRodUserSessionEntity entity = getHotRodEntity(); - entity.updated = entity.authenticatedClientSessions != null; - entity.authenticatedClientSessions = null; - } - } - - @Override - public boolean equals(Object o) { - return HotRodUserSessionEntityDelegate.entityEquals(this, o); - } - - @Override - public int hashCode() { - return HotRodUserSessionEntityDelegate.entityHashCode(this); - } -} diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodUserSessionMapStorage.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodUserSessionMapStorage.java deleted file mode 100644 index 3d7e99c09d1..00000000000 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/userSession/HotRodUserSessionMapStorage.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod.userSession; - -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.delegate.SimpleDelegateProvider; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapCrudOperations; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityDelegate; -import org.keycloak.models.map.userSession.MapUserSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntityDelegate; -import org.keycloak.storage.SearchableModelField; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN; - -public class HotRodUserSessionMapStorage extends ConcurrentHashMapStorage> { - - private final ConcurrentHashMapStorage> clientSessionStore; - - public HotRodUserSessionMapStorage(CrudOperations map, - StringKeyConverter keyConverter, - DeepCloner cloner, - Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates, - ConcurrentHashMapStorage> clientSessionStore - ) { - super(map, keyConverter, cloner, fieldPredicates); - this.clientSessionStore = clientSessionStore; - } - - private MapAuthenticatedClientSessionEntity wrapClientSessionEntityToClientSessionAwareDelegate(MapAuthenticatedClientSessionEntity d) { - return new MapAuthenticatedClientSessionEntityDelegate(new HotRodAuthenticatedClientSessionEntityDelegateProvider(d) { - @Override - public MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase() { - return clientSessionStore.read(d.getId()); - } - }); - } - - private MapUserSessionEntity wrapUserSessionEntityToClientSessionAwareDelegate(MapUserSessionEntity entity) { - if (entity == null) return null; - - return new MapUserSessionEntityDelegate(new SimpleDelegateProvider<>(entity)) { - - private boolean filterAndRemoveNotExpired(MapAuthenticatedClientSessionEntity clientSession) { - if (!clientSessionStore.exists(clientSession.getId())) { - // If client session does not exist, remove the reference to it from userSessionEntity loaded in this transaction - entity.removeAuthenticatedClientSession(clientSession.getClientId()); - return false; - } - - return true; - } - - @Override - public Set getAuthenticatedClientSessions() { - Set clientSessions = super.getAuthenticatedClientSessions(); - return clientSessions == null ? null : clientSessions.stream() - // Find whether client session still exists in Infinispan and if not, remove the reference from user session - .filter(this::filterAndRemoveNotExpired) - .map(HotRodUserSessionMapStorage.this::wrapClientSessionEntityToClientSessionAwareDelegate) - .collect(Collectors.toSet()); - } - - @Override - public Optional getAuthenticatedClientSession(String clientUUID) { - return super.getAuthenticatedClientSession(clientUUID) - // Find whether client session still exists in Infinispan and if not, remove the reference from user sessionZ - .filter(this::filterAndRemoveNotExpired) - .map(HotRodUserSessionMapStorage.this::wrapClientSessionEntityToClientSessionAwareDelegate); - } - - @Override - public void addAuthenticatedClientSession(MapAuthenticatedClientSessionEntity clientSession) { - super.addAuthenticatedClientSession(clientSession); - clientSessionStore.create(clientSession); - } - - @Override - public Boolean removeAuthenticatedClientSession(String clientUUID) { - Optional clientSession = getAuthenticatedClientSession(clientUUID); - if (!clientSession.isPresent()) { - return false; - } - return super.removeAuthenticatedClientSession(clientUUID) && clientSessionStore.delete(clientSession.get().getId()); - } - - @Override - public void clearAuthenticatedClientSessions() { - Set clientSessions = super.getAuthenticatedClientSessions(); - if (clientSessions != null) { - clientSessionStore.delete(QueryParameters.withCriteria( - DefaultModelCriteria.criteria() - .compare(HotRodAuthenticatedClientSessionEntity.ID, IN, clientSessions.stream() - .map(MapAuthenticatedClientSessionEntity::getId)) - )); - } - super.clearAuthenticatedClientSessions(); - } - }; - } - - - @Override - public MapUserSessionEntity read(String sKey) { - return wrapUserSessionEntityToClientSessionAwareDelegate(super.read(sKey)); - } - - @Override - public Stream read(QueryParameters queryParameters) { - return super.read(queryParameters).map(this::wrapUserSessionEntityToClientSessionAwareDelegate); - } - - @Override - public MapUserSessionEntity create(MapUserSessionEntity value) { - return wrapUserSessionEntityToClientSessionAwareDelegate(super.create(value)); - } - - @Override - public boolean delete(String key) { - MapUserSessionEntity uSession = read(key); - Set clientSessions = uSession.getAuthenticatedClientSessions(); - if (clientSessions != null) { - clientSessionStore.delete(QueryParameters.withCriteria( - DefaultModelCriteria.criteria() - .compare(HotRodAuthenticatedClientSessionEntity.ID, IN, clientSessions.stream() - .map(MapAuthenticatedClientSessionEntity::getId)) - )); - } - - return super.delete(key); - } - - @Override - public long delete(QueryParameters queryParameters) { - clientSessionStore.delete(QueryParameters.withCriteria( - DefaultModelCriteria.criteria() - .compare(HotRodAuthenticatedClientSessionEntity.ID, IN, read(queryParameters) - .flatMap(userSession -> Optional.ofNullable(userSession.getAuthenticatedClientSessions()).orElse(Collections.emptySet()).stream().map(AbstractEntity::getId))) - )); - - return super.delete(queryParameters); - } -} diff --git a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.locking.GlobalLockProviderFactory b/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.locking.GlobalLockProviderFactory deleted file mode 100644 index 292c4090048..00000000000 --- a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.locking.GlobalLockProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.hotRod.locking.HotRodGlobalLockProviderFactory diff --git a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory b/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory deleted file mode 100644 index 132921e3379..00000000000 --- a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory diff --git a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory b/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory deleted file mode 100644 index bd7910fe4e9..00000000000 --- a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory \ No newline at end of file diff --git a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index 00d6ad2c470..00000000000 --- a/model/map-hot-rod/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionSpi \ No newline at end of file diff --git a/model/map-hot-rod/src/main/resources/config/admin-events-cache-config.xml b/model/map-hot-rod/src/main/resources/config/admin-events-cache-config.xml deleted file mode 100644 index 0415e6e3839..00000000000 --- a/model/map-hot-rod/src/main/resources/config/admin-events-cache-config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - kc.HotRodAdminEventEntity - - - - - diff --git a/model/map-hot-rod/src/main/resources/config/auth-events-cache-config.xml b/model/map-hot-rod/src/main/resources/config/auth-events-cache-config.xml deleted file mode 100644 index c6482a9a82b..00000000000 --- a/model/map-hot-rod/src/main/resources/config/auth-events-cache-config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - kc.HotRodAuthEventEntity - - - - diff --git a/model/map-hot-rod/src/main/resources/config/auth-sessions-cache-config.xml b/model/map-hot-rod/src/main/resources/config/auth-sessions-cache-config.xml deleted file mode 100644 index c02e9cc3cb4..00000000000 --- a/model/map-hot-rod/src/main/resources/config/auth-sessions-cache-config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - kc.HotRodRootAuthenticationSessionEntity - - - diff --git a/model/map-hot-rod/src/main/resources/config/authz-cache-config.xml b/model/map-hot-rod/src/main/resources/config/authz-cache-config.xml deleted file mode 100644 index 9baed2aabea..00000000000 --- a/model/map-hot-rod/src/main/resources/config/authz-cache-config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - kc.HotRodResourceServerEntity - kc.HotRodResourceEntity - kc.HotRodScopeEntity - kc.HotRodPolicyEntity - kc.HotRodPermissionTicketEntity - - - - - diff --git a/model/map-hot-rod/src/main/resources/config/client-scopes-cache-config.xml b/model/map-hot-rod/src/main/resources/config/client-scopes-cache-config.xml deleted file mode 100644 index 993e5c03f09..00000000000 --- a/model/map-hot-rod/src/main/resources/config/client-scopes-cache-config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - kc.HotRodClientScopeEntity - - - diff --git a/model/map-hot-rod/src/main/resources/config/clients-cache-config.xml b/model/map-hot-rod/src/main/resources/config/clients-cache-config.xml deleted file mode 100644 index 9b1baa4cecf..00000000000 --- a/model/map-hot-rod/src/main/resources/config/clients-cache-config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - kc.HotRodClientEntity - - - diff --git a/model/map-hot-rod/src/main/resources/config/groups-cache-config.xml b/model/map-hot-rod/src/main/resources/config/groups-cache-config.xml deleted file mode 100644 index c272b0636b6..00000000000 --- a/model/map-hot-rod/src/main/resources/config/groups-cache-config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - kc.HotRodGroupEntity - - - diff --git a/model/map-hot-rod/src/main/resources/config/locks-cache-config.xml b/model/map-hot-rod/src/main/resources/config/locks-cache-config.xml deleted file mode 100644 index 4ee6c091cda..00000000000 --- a/model/map-hot-rod/src/main/resources/config/locks-cache-config.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - diff --git a/model/map-hot-rod/src/main/resources/config/realms-cache-config.xml b/model/map-hot-rod/src/main/resources/config/realms-cache-config.xml deleted file mode 100644 index af76c8440aa..00000000000 --- a/model/map-hot-rod/src/main/resources/config/realms-cache-config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - kc.HotRodRealmEntity - - - - - diff --git a/model/map-hot-rod/src/main/resources/config/roles-cache-config.xml b/model/map-hot-rod/src/main/resources/config/roles-cache-config.xml deleted file mode 100644 index 1dd34d273b0..00000000000 --- a/model/map-hot-rod/src/main/resources/config/roles-cache-config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - kc.HotRodRoleEntity - - - - diff --git a/model/map-hot-rod/src/main/resources/config/single-use-objects-cache-config.xml b/model/map-hot-rod/src/main/resources/config/single-use-objects-cache-config.xml deleted file mode 100644 index ce7ab9d7870..00000000000 --- a/model/map-hot-rod/src/main/resources/config/single-use-objects-cache-config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - kc.HotRodSingleUseObjectEntity - - - - - diff --git a/model/map-hot-rod/src/main/resources/config/user-login-failures-cache-config.xml b/model/map-hot-rod/src/main/resources/config/user-login-failures-cache-config.xml deleted file mode 100644 index c228183f370..00000000000 --- a/model/map-hot-rod/src/main/resources/config/user-login-failures-cache-config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - kc.HotRodUserLoginFailureEntity - - - - diff --git a/model/map-hot-rod/src/main/resources/config/user-sessions-cache-config.xml b/model/map-hot-rod/src/main/resources/config/user-sessions-cache-config.xml deleted file mode 100644 index 80f036cec07..00000000000 --- a/model/map-hot-rod/src/main/resources/config/user-sessions-cache-config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - kc.HotRodUserSessionEntity - kc.HotRodAuthenticatedClientSessionEntity - - - - diff --git a/model/map-hot-rod/src/main/resources/config/users-cache-config.xml b/model/map-hot-rod/src/main/resources/config/users-cache-config.xml deleted file mode 100644 index b654de52f49..00000000000 --- a/model/map-hot-rod/src/main/resources/config/users-cache-config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - kc.HotRodUserEntity - - - - diff --git a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/HotRodUndefinedValuesTest.java b/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/HotRodUndefinedValuesTest.java deleted file mode 100644 index 206149cb8e2..00000000000 --- a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/HotRodUndefinedValuesTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.hotRod; - -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.RealmEntityUndefinedValuesTest; -import org.keycloak.models.map.storage.hotRod.realm.HotRodRealmEntityDelegate; - -public class HotRodUndefinedValuesTest extends RealmEntityUndefinedValuesTest { - @Override - public MapRealmEntity newMapRealmEntity() { - return new HotRodRealmEntityDelegate(); - } -} diff --git a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilderTest.java b/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilderTest.java deleted file mode 100644 index 3bda7e27b3c..00000000000 --- a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilderTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.storage.hotRod; - -import org.junit.Test; -import org.keycloak.models.ClientModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.keycloak.models.ClientModel.SearchableFields.CLIENT_ID; -import static org.keycloak.models.ClientModel.SearchableFields.ID; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class IckleQueryMapModelCriteriaBuilderTest { - @Test - public void testSimpleIckleQuery() { - IckleQueryMapModelCriteriaBuilder v = new IckleQueryMapModelCriteriaBuilder<>(HotRodClientEntity.class); - IckleQueryMapModelCriteriaBuilder mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 3); - assertThat(mcb.getIckleQuery(), is(equalTo("FROM kc.HotRodClientEntity c WHERE (c.clientId = :clientId0)"))); - assertThat(mcb.getParameters().entrySet(), hasSize(1)); - assertThat(mcb.getParameters(), hasEntry("clientId0", 3)); - - - mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 4) - .compare(ID, ModelCriteriaBuilder.Operator.EQ, 5); - - assertThat(mcb.getIckleQuery(), is(equalTo("FROM kc.HotRodClientEntity c WHERE ((c.clientId = :clientId0) AND (c.id = :id0))"))); - assertThat(mcb.getParameters().entrySet(), hasSize(2)); - assertThat(mcb.getParameters(), allOf(hasEntry("clientId0", 4), hasEntry("id0", 5))); - } - - - @Test - public void testSimpleIckleQueryFlashedFromDefault() { - DefaultModelCriteria v = criteria(); - IckleQueryMapModelCriteriaBuilder mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 3).flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>(HotRodClientEntity.class)); - assertThat(mcb.getIckleQuery(), is(equalTo("FROM kc.HotRodClientEntity c WHERE (c.clientId = :clientId0)"))); - assertThat(mcb.getParameters().entrySet(), hasSize(1)); - assertThat(mcb.getParameters(), hasEntry("clientId0", 3)); - - - mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 4) - .compare(ID, ModelCriteriaBuilder.Operator.EQ, 5).flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>(HotRodClientEntity.class)); - - assertThat(mcb.getIckleQuery(), is(equalTo("FROM kc.HotRodClientEntity c WHERE ((c.clientId = :clientId0) AND (c.id = :id0))"))); - assertThat(mcb.getParameters().entrySet(), hasSize(2)); - assertThat(mcb.getParameters(), allOf(hasEntry("clientId0", 4), hasEntry("id0", 5))); - } - - @Test - public void testUser() { - final DefaultModelCriteria mcb = criteria(); - DefaultModelCriteria criteria = mcb.compare(UserModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, "realm1"); - criteria = criteria.compare(UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, ModelCriteriaBuilder.Operator.NOT_EXISTS); - criteria = mcb.and(criteria, mcb.or( - mcb.compare(UserModel.SearchableFields.USERNAME, ModelCriteriaBuilder.Operator.LIKE, "a"), - mcb.compare(UserModel.SearchableFields.EMAIL, ModelCriteriaBuilder.Operator.ILIKE, "a"), - mcb.compare(UserModel.SearchableFields.FIRST_NAME, ModelCriteriaBuilder.Operator.ILIKE, "a"), - mcb.compare(UserModel.SearchableFields.LAST_NAME, ModelCriteriaBuilder.Operator.ILIKE, "a") - )); - - IckleQueryMapModelCriteriaBuilder ickle = criteria.flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>(HotRodUserEntity.class)); - - assertThat(ickle.getIckleQuery(), is(equalTo("FROM kc.HotRodUserEntity c WHERE ((c.realmId = :realmId0) AND (c.serviceAccountClientLink IS NULL OR c.serviceAccountClientLink IS EMPTY) AND ((c.username LIKE :username0) OR (c.email LIKE :email0) OR (c.firstName LIKE :firstName0) OR (c.lastName LIKE :lastName0)))"))); - assertThat(ickle.getParameters().entrySet(), hasSize(5)); - assertThat(ickle.getParameters(), allOf(hasEntry("realmId0", "realm1"), hasEntry("username0", "a"), hasEntry("email0", "a"), hasEntry("firstName0", "a"), hasEntry("lastName0", "a"))); - - final DefaultModelCriteria mcb2 = criteria(); - criteria = mcb2.compare(UserModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, "realm1") - .compare(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE, ModelCriteriaBuilder.Operator.ILIKE, "a"); - - ickle = criteria.flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>(HotRodUserEntity.class)); - - assertThat(ickle.getIckleQuery(), is(equalTo("FROM kc.HotRodUserEntity c WHERE ((c.realmId = :realmId0) AND (c.usernameLowercase LIKE :usernameLowercase0))"))); - assertThat(ickle.getParameters().entrySet(), hasSize(2)); - assertThat(ickle.getParameters(), allOf(hasEntry("realmId0", "realm1"), hasEntry("usernameLowercase0", "a"))); - } -} \ No newline at end of file diff --git a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperatorsTest.java b/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperatorsTest.java deleted file mode 100644 index f690209a9f7..00000000000 --- a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperatorsTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.keycloak.models.map.storage.hotRod; - -import org.hamcrest.Matchers; -import org.junit.Test; - -import java.util.HashSet; -import java.util.Set; - -import static org.hamcrest.MatcherAssert.assertThat; - -public class IckleQueryOperatorsTest { - - @Test - public void testFindAvailableNamedParamSimple() { - Set existingNames = new HashSet<>(); - - String param = IckleQueryOperators.findAvailableNamedParam(existingNames, "clientId"); - - assertThat("should create the first ID", param, Matchers.equalTo("clientId0")); - } - - @Test - public void testFindAvailableNamedParamAlreadyExists() { - Set existingNames = new HashSet<>(); - existingNames.add("clientId0"); - - String param = IckleQueryOperators.findAvailableNamedParam(existingNames, "clientId"); - - assertThat("should create the next ID as clientId0 is already taken", param, Matchers.equalTo("clientId1")); - } - - @Test - public void testFindAvailableNamedParamIllegalCharacterInPrefix() { - Set existingNames = new HashSet<>(); - existingNames.add("clientid0"); - - String param = IckleQueryOperators.findAvailableNamedParam(existingNames, "client.id"); - - assertThat("should remove non-characters and non-numbers from the ID", param, Matchers.equalTo("clientid1")); - } - -} \ No newline at end of file diff --git a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtilsTest.java b/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtilsTest.java deleted file mode 100644 index ff5cd09e473..00000000000 --- a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtilsTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.storage.hotRod.common; - - -import org.junit.Test; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasSize; -import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.getMapValueFromSet; -import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.migrateMapToSet; -import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.migrateSetToMap; -import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.removeFromSetByMapKey; - -public class HotRodTypesUtilsTest { - - @Test - public void testMigrateMapToSet() { - // Test null map - assertThat(migrateMapToSet((Map) null, Map.Entry::getKey), nullValue()); - - Map m = new HashMap<>(); - m.put("key", "value"); - - assertThat(migrateMapToSet(m, e -> e.getKey() + "#" + e.getValue()), - contains("key#value")); - } - - @Test - public void testMigrateSetToMap() { - // Test null map - assertThat(migrateSetToMap((Set) null, Function.identity(), Function.identity()), nullValue()); - - Set s = new HashSet<>(); - s.add("key#value"); - - Map result = HotRodTypesUtils.migrateSetToMap(s, e -> e.split("#")[0], e -> e.split("#")[1]); - - assertThat(result.keySet(), hasSize(1)); - assertThat(result, hasEntry("key", "value")); - } - - @Test - public void testRemoveFromSetByMapKey() { - assertThat(removeFromSetByMapKey((Set) null, null, Function.identity()), is(false)); - assertThat(removeFromSetByMapKey(Collections.emptySet(), null, Function.identity()), is(false)); - - Set s = new HashSet<>(); - s.add("key#value"); - s.add("key1#value1"); - s.add("key2#value2"); - - // Remove existing - Set testSet = new HashSet<>(s); - assertThat(removeFromSetByMapKey(testSet, "key", e -> e.split("#")[0]), is(true)); - assertThat(testSet, hasSize(2)); - assertThat(testSet, containsInAnyOrder("key1#value1", "key2#value2")); - - // Remove not existing - testSet = new HashSet<>(s); - assertThat(removeFromSetByMapKey(testSet, "key3", e -> e.split("#")[0]), is(false)); - assertThat(testSet, hasSize(3)); - assertThat(testSet, containsInAnyOrder("key#value", "key1#value1", "key2#value2")); - } - - @Test - public void testGetMapValueFromSet() { - assertThat(getMapValueFromSet((Set) null, null, Function.identity(), Function.identity()), nullValue()); - assertThat(getMapValueFromSet(Collections.emptySet(), "key", Function.identity(), Function.identity()), nullValue()); - - Set s = new HashSet<>(); - s.add("key#value"); - s.add("key1#value1"); - s.add("key2#value2"); - - // search existing - assertThat(getMapValueFromSet(s, "key", e -> e.split("#")[0], e -> e.split("#")[1]), is("value")); - assertThat(getMapValueFromSet(s, "key1", e -> e.split("#")[0], e -> e.split("#")[1]), is("value1")); - - // Search not existing - assertThat(getMapValueFromSet(s, "key3", e -> e.split("#")[0], e -> e.split("#")[1]), nullValue()); - } -} \ No newline at end of file diff --git a/model/map-jpa/pom.xml b/model/map-jpa/pom.xml deleted file mode 100644 index 81d1ecb7ceb..00000000000 --- a/model/map-jpa/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - keycloak-model-pom - org.keycloak - 999.0.0-SNAPSHOT - - 4.0.0 - - keycloak-model-map-jpa - Keycloak Model Map JPA - - - - org.keycloak - keycloak-model-map - ${project.version} - - - jakarta.persistence - jakarta.persistence-api - - - org.hibernate.orm - hibernate-core - - - org.liquibase - liquibase-core - - - - - - org.hibernate.orm.tooling - hibernate-enhance-maven-plugin - ${hibernate-orm.plugin.version} - - - - true - true - - - enhance - - - - - - - diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java deleted file mode 100644 index 09d734f5cbd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.storage.jpa; - -public interface Constants { - public static final Integer CURRENT_SCHEMA_VERSION_ADMIN_EVENT = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTH_EVENT = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTH_SESSION = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_POLICY = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER = 1; - public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE = 1; - public static final Integer CURRENT_SCHEMA_VERSION_CLIENT = 1; - public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SESSION = 1; - public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SCOPE = 1; - public static final Integer CURRENT_SCHEMA_VERSION_COMPONENT = 1; - public static final Integer CURRENT_SCHEMA_VERSION_GROUP = 1; - public static final Integer CURRENT_SCHEMA_VERSION_REALM = 1; - public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1; - public static final Integer CURRENT_SCHEMA_VERSION_ROOT_AUTH_SESSION = 1; - public static final Integer CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT = 1; - public static final Integer CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE = 1; - public static final Integer CURRENT_SCHEMA_VERSION_USER = 2; - public static final Integer CURRENT_SCHEMA_VERSION_USER_CONSENT = 1; - public static final Integer CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY = 1; - public static final Integer CURRENT_SCHEMA_VERSION_USER_SESSION = 1; - public static final Integer CURRENT_SCHEMA_VERSION_LOCK = 1; -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/EventListenerIntegrator.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/EventListenerIntegrator.java deleted file mode 100644 index 7a622a50e02..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/EventListenerIntegrator.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import org.hibernate.boot.Metadata; -import org.hibernate.dialect.CockroachDialect; -import org.hibernate.engine.OptimisticLockStyle; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.service.spi.EventListenerRegistry; -import org.hibernate.event.spi.EventType; -import org.hibernate.integrator.spi.Integrator; -import org.hibernate.mapping.RootClass; -import org.hibernate.service.spi.SessionFactoryServiceRegistry; -import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaAutoFlushListener; -import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaEntityVersionListener; -import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaOptimisticLockingListener; - -/** - * Adding listeners to Hibernate's entity manager for the JPA Map store. - */ -public class EventListenerIntegrator implements Integrator { - - @Override - public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactoryImplementor, - SessionFactoryServiceRegistry sessionFactoryServiceRegistry) { - final EventListenerRegistry eventListenerRegistry = - sessionFactoryServiceRegistry.getService(EventListenerRegistry.class); - - if (metadata.getDatabase().getDialect() instanceof CockroachDialect) { - // CockroachDB will always use serializable transactions, therefore no optimistic locking is necessary - metadata.getEntityBindings().forEach(persistentClass -> { - if (persistentClass instanceof RootClass) { - RootClass root = (RootClass) persistentClass; - root.setOptimisticLockStyle(OptimisticLockStyle.NONE); - root.setVersion(null); - root.setDeclaredVersion(null); - } - }); - // If the version column should be updated with an incrementing number on each change in the future, - // implement a listener similar to JpaOptimisticLockingListener to increment it. - } else { - eventListenerRegistry.appendListeners(EventType.PRE_INSERT, JpaOptimisticLockingListener.INSTANCE); - eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, JpaOptimisticLockingListener.INSTANCE); - eventListenerRegistry.appendListeners(EventType.PRE_DELETE, JpaOptimisticLockingListener.INSTANCE); - } - - eventListenerRegistry.appendListeners(EventType.PRE_INSERT, JpaEntityVersionListener.INSTANCE); - eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, JpaEntityVersionListener.INSTANCE); - eventListenerRegistry.appendListeners(EventType.PRE_DELETE, JpaEntityVersionListener.INSTANCE); - - // replace auto-flush listener - eventListenerRegistry.setListeners(EventType.AUTO_FLUSH, JpaAutoFlushListener.INSTANCE); - } - - @Override - public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor, - SessionFactoryServiceRegistry sessionFactoryServiceRegistry) { - - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaAttributeEntity.java deleted file mode 100644 index 2237620deff..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaAttributeEntity.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.Column; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.MappedSuperclass; -import org.hibernate.annotations.Nationalized; - -/** - * Interface for jpa child entities which are in form of attributes. In other words - * it contains name and value. - */ -@MappedSuperclass -public abstract class JpaAttributeEntity implements JpaChildEntity { - - @Id - @Column - @GeneratedValue - private UUID id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="fk_root") - private E root; - - @Column - private String name; - - @Nationalized - @Column - private String value; - - public JpaAttributeEntity() { - } - - public JpaAttributeEntity(E root, String name, String value) { - this.root = root; - this.name = name; - this.value = value; - } - - public UUID getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @Override - public E getParent() { - return root; - } - - public void setParent(E root) { - this.root = root; - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaAttributeEntity)) return false; - JpaAttributeEntity that = (JpaAttributeEntity) obj; - return Objects.equals(getParent(), that.getParent()) && - Objects.equals(getName(), that.getName()) && - Objects.equals(getValue(), that.getValue()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaAttributeEntityWithHashValue.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaAttributeEntityWithHashValue.java deleted file mode 100644 index 076424dfd3b..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaAttributeEntityWithHashValue.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import jakarta.persistence.Column; -import jakarta.persistence.MappedSuperclass; - -/** - * Enhances {@link JpaAttributeEntity} with value_hash column. - */ -@MappedSuperclass -public abstract class JpaAttributeEntityWithHashValue extends JpaAttributeEntity { - - @Column(insertable = false, updatable = false) - private byte[] value_hash; - - public JpaAttributeEntityWithHashValue() { - } - - public JpaAttributeEntityWithHashValue(E root, String name, String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaChildEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaChildEntity.java deleted file mode 100644 index 24e88985287..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaChildEntity.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022. 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.storage.jpa; - -import java.io.Serializable; - -/** - * Interface for all child entities for JPA map storage. - */ -public interface JpaChildEntity extends Serializable { - - /** - * Parent entity that should get its optimistic locking version updated upon changes in the child - */ - R getParent(); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaDelegateProvider.java deleted file mode 100644 index 3ff5c0e39e3..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaDelegateProvider.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022. 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.storage.jpa; - -import org.keycloak.models.ModelIllegalStateException; -import org.keycloak.models.map.common.AbstractEntity; - -/** - * Base class for all delegate providers for the JPA storage. - * - * Wraps the delegate so that it can be safely updated during lazy loading. - */ -public abstract class JpaDelegateProvider { - private T delegate; - - protected JpaDelegateProvider(T delegate) { - this.delegate = delegate; - } - - protected T getDelegate() { - return delegate; - } - - /** - * Validates the new entity. - * - * Will throw {@link ModelIllegalStateException} if the entity has been deleted or changed in the meantime. - */ - protected void setDelegate(T newDelegate) { - if (newDelegate == null) { - throw new ModelIllegalStateException("Unable to retrieve entity: " + delegate.getClass().getName() + "#" + delegate.getId()); - } - if (newDelegate instanceof JpaRootVersionedEntity && delegate instanceof JpaRootVersionedEntity) { - if (((JpaRootVersionedEntity) newDelegate).getVersion() != ((JpaRootVersionedEntity) delegate).getVersion()) { - throw new ModelIllegalStateException("Version of entity changed between two loads: " + delegate.getClass().getName() + "#" + delegate.getId()); - } - } - this.delegate = newDelegate; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorage.java deleted file mode 100644 index 14dcb0e0a17..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorage.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import jakarta.persistence.EntityManager; -import jakarta.persistence.LockModeType; -import jakarta.persistence.PersistenceException; -import jakarta.persistence.TypedQuery; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Order; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.hibernate.Session; -import org.hibernate.internal.SessionImpl; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.utils.LockObjectsForModification; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER; -import static org.keycloak.models.map.storage.jpa.PaginationUtils.paginateQuery; -import static org.keycloak.utils.StreamsUtil.closing; - -public abstract class JpaMapStorage implements MapStorage { - - private static final Logger logger = Logger.getLogger(JpaMapStorage.class); - private final KeycloakSession session; - private final Class entityType; - private final Class modelType; - private final boolean isExpirableEntity; - protected EntityManager em; - - public JpaMapStorage(KeycloakSession session, Class entityType, Class modelType, EntityManager em) { - this.session = session; - this.em = em; - this.entityType = entityType; - this.modelType = modelType; - this.isExpirableEntity = ExpirableEntity.class.isAssignableFrom(entityType); - } - - protected abstract Selection selectCbConstruct(CriteriaBuilder cb, Root root); - protected abstract void setEntityVersion(JpaRootEntity entity); - protected abstract JpaModelCriteriaBuilder createJpaModelCriteriaBuilder(); - protected abstract E mapToEntityDelegate(RE original); - - private final HashMap cacheWithinSession = new HashMap<>(); - - /** - * Use the cache within the session to ensure that there is only one instance per entity within the current session. - */ - protected E mapToEntityDelegateUnique(RE original) { - if (original == null) { - return null; - } - E entity = cacheWithinSession.get(original.getId()); - if (entity == null) { - entity = mapToEntityDelegate(original); - cacheWithinSession.put(original.getId(), entity); - } - return entity; - } - - @Override - public E create(E mapEntity) { - RE jpaEntity = entityType.cast(CLONER.from(mapEntity)); - if (mapEntity.getId() == null) { - jpaEntity.setId(StringKeyConverter.UUIDKey.INSTANCE.yieldNewUniqueKey().toString()); - } - logger.tracef("tx %d: create entity %s", hashCode(), jpaEntity.getId()); - setEntityVersion(jpaEntity); - em.persist(jpaEntity); - return mapToEntityDelegateUnique(jpaEntity); - } - - @Override - public E read(String key) { - if (key == null) return null; - E e = null; - if (!LockObjectsForModification.isEnabled(session, modelType)) { - e = cacheWithinSession.get(key); - } - if (e == null) { - UUID uuid = StringKeyConverter.UUIDKey.INSTANCE.fromStringSafe(key); - if (uuid == null) return null; - e = mapToEntityDelegateUnique( - LockObjectsForModification.isEnabled(session, modelType) ? - em.find(entityType, uuid, LockModeType.PESSIMISTIC_WRITE) : - em.find(entityType, uuid) - ); - } - return e != null && isExpirableEntity && isExpired((ExpirableEntity) e, true) ? null : e; - } - - private final static String JPA_MAP_CACHE = "keycloak.jpamap.cache"; - - @Override - @SuppressWarnings("unchecked") - public Stream read(QueryParameters queryParameters) { - Map> cache = getQueryCache(); - QueryCacheKey queryCacheKey = new QueryCacheKey(queryParameters, modelType); - if (!LockObjectsForModification.isEnabled(this.session, modelType)) { - List previousResult = cache.get(queryCacheKey); - SessionImpl session = (SessionImpl) em.unwrap(Session.class); - // only do dirty checking if there is a previously cached result that would match the query - if (previousResult != null) { - // if the session is dirty, data has been modified, and the cache must not be used - // check if there are queued actions already, as this allows us to skip the expensive dirty check - if (!session.getActionQueue().areInsertionsOrDeletionsQueued() && session.getActionQueue().numberOfUpdates() == 0 && session.getActionQueue().numberOfCollectionUpdates() == 0 && - !session.isDirty()) { - logger.tracef("tx %d: cache hit for %s/%s%s", hashCode(), queryParameters, queryCacheKey, getShortStackTrace()); - return closing(previousResult.stream()).map(this::mapToEntityDelegateUnique); - } else { - logger.tracef("tx %d: cache ignored due to dirty session", hashCode()); - } - } - } - logger.tracef("tx %d: cache miss for %s/%s%s", hashCode(), queryParameters, queryCacheKey, getShortStackTrace()); - - JpaModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder() - .flashToModelCriteriaBuilder(createJpaModelCriteriaBuilder()); - - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(entityType); - Root root = query.from(entityType); - query.select(selectCbConstruct(cb, root)); - if (mcb.isDistinct()) query.distinct(true); - - //ordering - if (!queryParameters.getOrderBy().isEmpty()) { - List orderByList = new LinkedList<>(); - for (QueryParameters.OrderBy order : queryParameters.getOrderBy()) { - switch (order.getOrder()) { - case ASCENDING: - orderByList.add(cb.asc(root.get(order.getModelField().getName()))); - break; - case DESCENDING: - orderByList.add(cb.desc(root.get(order.getModelField().getName()))); - break; - default: - throw new UnsupportedOperationException("Unknown ordering."); - } - } - query.orderBy(orderByList); - } - - JpaPredicateFunction predicateFunc = mcb.getPredicateFunc(); - if (this.isExpirableEntity && (queryParameters.getLimit() != null || queryParameters.getOffset() != null)) { - // only when using pagination exclude expired entities in the query directly - // for all other queries, remove the expired results later as those additional predicates might confuse the database - // to use a bad index (see: CockroachDB), and we assume that expired entities are cleaned from the DB regularly - predicateFunc = predicateFunc != null ? predicateFunc.andThen(predicate -> cb.and(predicate, notExpired(cb, query::subquery, root))) - : this::notExpired; - } - if (predicateFunc != null) query.where(predicateFunc.apply(cb, query::subquery, root)); - - TypedQuery emQuery = paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit()); - - if (LockObjectsForModification.isEnabled(session, modelType)) { - emQuery = emQuery.setLockMode(LockModeType.PESSIMISTIC_WRITE); - } - - try { - // In order to cache the result, the full result needs to be retrieved. - // There is also no difference to that in Hibernate, as Hibernate will first retrieve all elements from the ResultSet. - List resultList = emQuery.getResultList(); - if (isExpirableEntity) { - // remove expired entities when those haven't been excluded by a predicate - resultList = resultList.stream().filter(e -> !isExpired((ExpirableEntity) e, true)).collect(Collectors.toList()); - } - cache.put(queryCacheKey, resultList); - - return closing(resultList.stream()).map(this::mapToEntityDelegateUnique); - } catch (PersistenceException pe) { - // handle exception that could occur on auto-flush when the query is executed - throw PersistenceExceptionConverter.convert(pe.getCause() != null ? pe.getCause() : pe); - } - } - - private Map> getQueryCache() { - //noinspection unchecked - Map> cache = (Map>) em.unwrap(Session.class).getProperties().get(JPA_MAP_CACHE); - if (cache == null) { - cache = new HashMap<>(); - em.unwrap(Session.class).setProperty(JPA_MAP_CACHE, cache); - } - return cache; - } - - public static void clearQueryCache(Session session) { - logger.tracef("query cache cleared"); - //noinspection unchecked - Map queryCache = (HashMap, Map>) session.getProperties().get(JPA_MAP_CACHE); - if (queryCache != null) { - // Can't set null as a property values as it is not serializable. Clearing each map so that the current query result might be saved. - queryCache.clear(); - } - } - - @Override - @SuppressWarnings("unchecked") - public long getCount(QueryParameters queryParameters) { - JpaModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder() - .flashToModelCriteriaBuilder(createJpaModelCriteriaBuilder()); - - CriteriaBuilder cb = em.getCriteriaBuilder(); - - CriteriaQuery countQuery = cb.createQuery(Long.class); - Root root = countQuery.from(entityType); - countQuery.select(cb.count(root)); - - JpaPredicateFunction predicateFunc = mcb.getPredicateFunc(); - if (predicateFunc != null) countQuery.where(predicateFunc.apply(cb, countQuery::subquery, root)); - - try { - return em.createQuery(countQuery).getSingleResult(); - } catch (PersistenceException pe) { - // handle exception that could occur on auto-flush when the query is executed - throw PersistenceExceptionConverter.convert(pe.getCause() != null ? pe.getCause() : pe); - } - } - - @Override - public boolean delete(String key) { - if (key == null) return false; - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key); - if (uuid == null) return false; - removeFromCache(key); - // First find the entity, as just trying to remove it will throw an EntityNotFoundException - RE entity = em.find(entityType, uuid); - if (entity != null) { - em.remove(entity); - } - logger.tracef("tx %d: delete entity %s", hashCode(), key); - return true; - } - - protected void removeFromCache(String key) { - cacheWithinSession.remove(key); - } - - @Override - @SuppressWarnings("unchecked") - public long delete(QueryParameters queryParameters) { - JpaModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder() - .flashToModelCriteriaBuilder(createJpaModelCriteriaBuilder()); - - CriteriaBuilder cb = em.getCriteriaBuilder(); - - // Remove all entities that are in the persistence context and that match the criteria. - // This avoids calling flush and clear which would detach all other unrelated entities as well. - int[] removed = {0}; - MapModelCriteriaBuilder mapMcb = queryParameters.getModelCriteriaBuilder().flashToModelCriteriaBuilder(createCriteriaBuilderMap()); - cacheWithinSession.entrySet().removeIf(entry -> { - if (mapMcb.getKeyFilter().test(entry.getKey()) && mapMcb.getEntityFilter().test(entry.getValue())) { - em.remove(em.getReference(entityType, UUIDKey.INSTANCE.fromString(entry.getKey()))); - removed[0]++; - return true; - } else { - return false; - } - }); - - CriteriaDelete deleteQuery = cb.createCriteriaDelete(entityType); - - Root root = deleteQuery.from(entityType); - - JpaPredicateFunction predicateFunc = mcb.getPredicateFunc(); - if (predicateFunc != null) deleteQuery.where(predicateFunc.apply(cb, deleteQuery::subquery, root)); - - try { - return em.createQuery(deleteQuery).executeUpdate() + removed[0]; - } catch (PersistenceException pe) { - // handle exception that could occur on auto-flush when the query is executed - throw PersistenceExceptionConverter.convert(pe.getCause() != null ? pe.getCause() : pe); - } - } - - private MapModelCriteriaBuilder createCriteriaBuilderMap() { - return new MapModelCriteriaBuilder<>(StringKeyConverter.StringKey.INSTANCE, MapFieldPredicates.getPredicates(modelType)); - } - - private Predicate notExpired(final CriteriaBuilder cb, final JpaSubqueryProvider query, final Root root) { - return cb.or(cb.greaterThan(root.get("expiration"), Time.currentTimeMillis()), - cb.isNull(root.get("expiration"))); - } - - private static class QueryCacheKey { - private final String queryString; - private final Integer queryLimit; - private final Integer queryOffset; - private final Class modelType; - private final List> queryOrderBy; - - public QueryCacheKey(QueryParameters query, Class modelType) { - // copy over all fields from the query that relevant for caching - this.queryString = query.getModelCriteriaBuilder().toString(); - this.queryLimit = query.getLimit(); - this.queryOffset = query.getOffset(); - this.queryOrderBy = new ArrayList<>(query.getOrderBy()); - this.modelType = modelType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - QueryCacheKey that = (QueryCacheKey) o; - return Objects.equals(queryString, that.queryString) - && Objects.equals(queryLimit, that.queryLimit) - && Objects.equals(queryOffset, that.queryOffset) - && Objects.equals(queryOrderBy, that.queryOrderBy) - && Objects.equals(modelType, that.modelType); - } - - @Override - public int hashCode() { - return Objects.hash(queryString, queryLimit, queryOffset, queryOrderBy, modelType); - } - - @Override - public String toString() { - return "QueryCacheKey{" + - "queryString='" + queryString + '\'' + - ", queryMaxResults=" + queryLimit + - ", queryFirstResult=" + queryOffset + - ", queryOrderBy=" + queryOrderBy + - ", modelType=" + modelType.getName() + - '}'; - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java deleted file mode 100644 index d4229998e5a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.storage.jpa; - -import jakarta.persistence.EntityManager; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakTransaction; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag; - -public class JpaMapStorageProvider implements MapStorageProvider { - - private final JpaMapStorageProviderFactory factory; - private final KeycloakSession session; - private final EntityManager em; - - private final int factoryId; - - public JpaMapStorageProvider(JpaMapStorageProviderFactory factory, KeycloakSession session, EntityManager em, boolean jtaEnabled, int factoryId) { - this.factory = factory; - this.session = session; - this.em = em; - this.factoryId = factoryId; - - // Create the JPA transaction and enlist it if needed. - // Don't enlist if JTA is enabled as it has been enlisted with JTA automatically. - if (!jtaEnabled) { - KeycloakTransaction jpaTransaction = new JpaTransactionWrapper(em.getTransaction()); - session.getTransactionManager().enlist(jpaTransaction); - } - } - - @Override - public void close() { - em.close(); - } - - @Override - @SuppressWarnings("unchecked") - public MapStorage getMapStorage(Class modelType, Flag... flags) { - // validate and update the schema for the storage. - this.factory.validateAndUpdateSchema(this.session, modelType); - - return SessionAttributesUtils.createMapStorageIfAbsent(session, JpaMapStorageProvider.class, modelType, factoryId, () -> factory.createMapStorage(session, modelType, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java deleted file mode 100644 index 9901e271a33..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * 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.storage.jpa; - -import static org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider.Status.VALID; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; - -import javax.naming.InitialContext; -import javax.naming.NamingException; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.ValidationMode; -import jakarta.persistence.spi.PersistenceUnitTransactionType; -import javax.sql.DataSource; -import jakarta.transaction.HeuristicMixedException; -import jakarta.transaction.HeuristicRollbackException; -import jakarta.transaction.InvalidTransactionException; -import jakarta.transaction.NotSupportedException; -import jakarta.transaction.RollbackException; -import jakarta.transaction.SystemException; -import jakarta.transaction.Transaction; -import liquibase.GlobalConfiguration; - -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.internal.SessionImpl; -import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; -import org.hibernate.jpa.boot.internal.PersistenceXmlParser; -import org.hibernate.jpa.boot.spi.Bootstrap; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.common.Profile; -import org.keycloak.common.util.StackUtil; -import org.keycloak.common.util.StringPropertyReplacer; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.events.Event; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.ModelException; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.locking.GlobalLockProvider; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.lock.MapLockEntity; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntityImpl; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntityImpl; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntityImpl; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntityImpl; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntityImpl; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntityImpl; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntityImpl; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntityImpl; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntityImpl; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntityImpl; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.storage.jpa.authSession.JpaRootAuthenticationSessionMapStorage; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionEntity; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity; -import org.keycloak.models.map.storage.jpa.authorization.permission.JpaPermissionMapStorage; -import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity; -import org.keycloak.models.map.storage.jpa.authorization.policy.JpaPolicyMapStorage; -import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity; -import org.keycloak.models.map.storage.jpa.authorization.resource.JpaResourceMapStorage; -import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.JpaResourceServerMapStorage; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerEntity; -import org.keycloak.models.map.storage.jpa.authorization.scope.JpaScopeMapStorage; -import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEntity; -import org.keycloak.models.map.storage.jpa.client.JpaClientMapStorage; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity; -import org.keycloak.models.map.storage.jpa.clientScope.JpaClientScopeMapStorage; -import org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeEntity; -import org.keycloak.models.map.storage.jpa.event.admin.JpaAdminEventMapStorage; -import org.keycloak.models.map.storage.jpa.event.admin.entity.JpaAdminEventEntity; -import org.keycloak.models.map.storage.jpa.event.auth.JpaAuthEventMapStorage; -import org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventEntity; -import org.keycloak.models.map.storage.jpa.group.JpaGroupMapStorage; -import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity; -import org.keycloak.models.map.storage.jpa.lock.JpaLockMapStorage; -import org.keycloak.models.map.storage.jpa.lock.entity.JpaLockEntity; -import org.keycloak.models.map.storage.jpa.loginFailure.JpaUserLoginFailureMapStorage; -import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity; -import org.keycloak.models.map.storage.jpa.realm.JpaRealmMapStorage; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity; -import org.keycloak.models.map.storage.jpa.role.JpaRoleMapStorage; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity; -import org.keycloak.models.map.storage.jpa.singleUseObject.JpaSingleUseObjectMapStorage; -import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider; -import org.keycloak.models.map.storage.jpa.userSession.JpaUserSessionMapStorage; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionEntity; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity; -import org.keycloak.models.map.storage.jpa.user.JpaUserMapStorage; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityEntity; -import org.keycloak.models.map.user.MapUserCredentialEntity; -import org.keycloak.models.map.user.MapUserCredentialEntityImpl; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.provider.ProviderConfigurationBuilder; -import org.keycloak.sessions.RootAuthenticationSessionModel; -import org.keycloak.transaction.JtaTransactionManagerLookup; - -public class JpaMapStorageProviderFactory implements - AmphibianProviderFactory, - MapStorageProviderFactory, - EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "jpa"; - private static final Logger logger = Logger.getLogger(JpaMapStorageProviderFactory.class); - - public static final String HIBERNATE_DEFAULT_SCHEMA = "hibernate.default_schema"; - - private static final long DEFAULT_LOCK_TIMEOUT = 10000; - - private volatile EntityManagerFactory emf; - private final Set> validatedModels = ConcurrentHashMap.newKeySet(); - private Config.Scope config; - - private final int factoryId = SessionAttributesUtils.grabNewFactoryIdentifier(); - private String databaseShortName; - - // Object instances for each single JpaMapStorageProviderFactory instance per model type. - // Used to synchronize on when validating the model type area. - private final ConcurrentHashMap, Object> SYNC_MODELS = new ConcurrentHashMap<>(); - - public final static DeepCloner CLONER = new DeepCloner.Builder() - //auth-sessions - .constructor(JpaRootAuthenticationSessionEntity.class, JpaRootAuthenticationSessionEntity::new) - .constructor(JpaAuthenticationSessionEntity.class, JpaAuthenticationSessionEntity::new) - //authorization - .constructor(JpaResourceServerEntity.class, JpaResourceServerEntity::new) - .constructor(JpaResourceEntity.class, JpaResourceEntity::new) - .constructor(JpaScopeEntity.class, JpaScopeEntity::new) - .constructor(JpaPermissionEntity.class, JpaPermissionEntity::new) - .constructor(JpaPolicyEntity.class, JpaPolicyEntity::new) - //clients - .constructor(JpaClientEntity.class, JpaClientEntity::new) - .constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new) - //client-scopes - .constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new) - //events - .constructor(JpaAdminEventEntity.class, JpaAdminEventEntity::new) - .constructor(JpaAuthEventEntity.class, JpaAuthEventEntity::new) - //groups - .constructor(JpaGroupEntity.class, JpaGroupEntity::new) - //realms - .constructor(JpaRealmEntity.class, JpaRealmEntity::new) - .constructor(JpaComponentEntity.class, JpaComponentEntity::new) - .constructor(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl::new) - .constructor(MapAuthenticationFlowEntity.class, MapAuthenticationFlowEntityImpl::new) - .constructor(MapAuthenticatorConfigEntity.class, MapAuthenticatorConfigEntityImpl::new) - .constructor(MapClientInitialAccessEntity.class, MapClientInitialAccessEntityImpl::new) - .constructor(MapIdentityProviderEntity.class, MapIdentityProviderEntityImpl::new) - .constructor(MapIdentityProviderMapperEntity.class, MapIdentityProviderMapperEntityImpl::new) - .constructor(MapOTPPolicyEntity.class, MapOTPPolicyEntityImpl::new) - .constructor(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl::new) - .constructor(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl::new) - .constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new) - //roles - .constructor(JpaRoleEntity.class, JpaRoleEntity::new) - //single-use-objects - .constructor(JpaSingleUseObjectEntity.class, JpaSingleUseObjectEntity::new) - //user-login-failures - .constructor(JpaUserLoginFailureEntity.class, JpaUserLoginFailureEntity::new) - //users - .constructor(JpaUserEntity.class, JpaUserEntity::new) - .constructor(JpaUserConsentEntity.class, JpaUserConsentEntity::new) - .constructor(JpaUserFederatedIdentityEntity.class, JpaUserFederatedIdentityEntity::new) - .constructor(MapUserCredentialEntity.class, MapUserCredentialEntityImpl::new) - //user/client session - .constructor(JpaClientSessionEntity.class, JpaClientSessionEntity::new) - .constructor(JpaUserSessionEntity.class, JpaUserSessionEntity::new) - //lock - .constructor(JpaLockEntity.class, JpaLockEntity::new) - .build(); - - private static final Map, BiFunction> MODEL_TO_STORE = new HashMap<>(); - static { - //auth-sessions - MODEL_TO_STORE.put(RootAuthenticationSessionModel.class, JpaRootAuthenticationSessionMapStorage::new); - //authorization - MODEL_TO_STORE.put(ResourceServer.class, JpaResourceServerMapStorage::new); - MODEL_TO_STORE.put(Resource.class, JpaResourceMapStorage::new); - MODEL_TO_STORE.put(Scope.class, JpaScopeMapStorage::new); - MODEL_TO_STORE.put(PermissionTicket.class, JpaPermissionMapStorage::new); - MODEL_TO_STORE.put(Policy.class, JpaPolicyMapStorage::new); - //clients - MODEL_TO_STORE.put(ClientModel.class, JpaClientMapStorage::new); - //client-scopes - MODEL_TO_STORE.put(ClientScopeModel.class, JpaClientScopeMapStorage::new); - //events - MODEL_TO_STORE.put(AdminEvent.class, JpaAdminEventMapStorage::new); - MODEL_TO_STORE.put(Event.class, JpaAuthEventMapStorage::new); - //groups - MODEL_TO_STORE.put(GroupModel.class, JpaGroupMapStorage::new); - //realms - MODEL_TO_STORE.put(RealmModel.class, JpaRealmMapStorage::new); - //roles - MODEL_TO_STORE.put(RoleModel.class, JpaRoleMapStorage::new); - //single-use-objects - MODEL_TO_STORE.put(SingleUseObjectValueModel.class, JpaSingleUseObjectMapStorage::new); - //user-login-failures - MODEL_TO_STORE.put(UserLoginFailureModel.class, JpaUserLoginFailureMapStorage::new); - //users - MODEL_TO_STORE.put(UserModel.class, JpaUserMapStorage::new); - //sessions - MODEL_TO_STORE.put(UserSessionModel.class, JpaUserSessionMapStorage::new); - //locks - MODEL_TO_STORE.put(MapLockEntity.class, JpaLockMapStorage::new); - } - - private boolean jtaEnabled; - private JtaTransactionManagerLookup jtaLookup; - - public MapStorage createMapStorage(KeycloakSession session, Class modelType, EntityManager em) { - return MODEL_TO_STORE.get(modelType).apply(session, em); - } - - @Override - public MapStorageProvider create(KeycloakSession session) { - lazyInit(); - - return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, JpaMapStorageProvider.class, - session1 -> new JpaMapStorageProvider(this, session, PersistenceExceptionConverter.create(session, getEntityManager()), this.jtaEnabled, factoryId)); - } - - protected EntityManager getEntityManager() { - EntityManager em = emf.createEntityManager(); - - // This is a workaround for Hibernate not supporting jakarta.persistence.lock.timeout - // config option for Postgresql/CockroachDB - https://hibernate.atlassian.net/browse/HHH-16181 - if ("postgresql".equals(databaseShortName) || "cockroachdb".equals(databaseShortName)) { - Long lockTimeout = config.getLong("lockTimeout", DEFAULT_LOCK_TIMEOUT); - if (lockTimeout >= 0) { - em.unwrap(SessionImpl.class) - .doWork(connection -> { - // 'SET LOCAL lock_timeout = ...' can't be used with parameters in a prepared statement, leads to an - // 'ERROR: syntax error at or near "$1"' - // on PostgreSQL. - // Using 'set_config()' instead as described here: https://www.postgresql.org/message-id/CAKFQuwbMaoO9%3DVUY1K0Nz5YBDyE6YQ9A_A6ncCxD%2Bt0yK1AxJg%40mail.gmail.com - // See https://www.postgresql.org/docs/13/functions-admin.html for the documentation on this function - try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT set_config('lock_timeout', ?, true)")) { - preparedStatement.setString(1, String.valueOf(lockTimeout)); - ResultSet resultSet = preparedStatement.executeQuery(); - resultSet.close(); - } - }); - } - } - return em; - } - - @Override - public void init(Config.Scope config) { - this.config = config; - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class); - jtaEnabled = jtaLookup != null && jtaLookup.getTransactionManager() != null; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public String getHelpText() { - return "JPA Map Storage"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public void close() { - if (emf != null) { - emf.close(); - } - this.validatedModels.clear(); - } - - volatile boolean initialized; - - private void lazyInit() { - /* - On Quarkus, and EMF can be created even when the database is currently not available. - Closing the EMF is not an option, as it is managed by Quarkus. - Therefore, try to initialize it as often as needed, especially for the addSpecificNamedQueries() - which would cause failures later if not initialized here. - */ - if (!initialized) { - synchronized (this) { - if (emf == null) { - this.emf = createEntityManagerFactory(); - } - if (!initialized) { - JpaMapUtils.addSpecificNamedQueries(emf); - - // consistency check for transaction handling, as this would lead to data-inconsistencies as changes wouldn't commit when expected - if (jtaEnabled && !this.emf.getProperties().get(AvailableSettings.JAKARTA_TRANSACTION_TYPE).equals(PersistenceUnitTransactionType.JTA.name())) { - throw new ModelException("Consistency check failed: If Keycloak is run with JTA, the Entity Manager for JPA map storage should be run with JTA as well."); - } - - // consistency check for auto-commit, as this would lead to data-inconsistencies as changes wouldn't roll back when expected - EntityManager em = getEntityManager(); - try { - em.unwrap(SessionImpl.class).doWork(connection -> { - if (connection.getAutoCommit()) { - throw new ModelException("The database connection must not use auto-commit. For Quarkus, auto-commit was off once JTA was enabled for the EntityManager."); - } - }); - } finally { - em.close(); - } - initialized = true; - } - } - } - } - - protected EntityManagerFactory createEntityManagerFactory() { - logger.debugf("Initializing JPA connections %s", StackUtil.getShortStackTrace()); - - Map properties = new HashMap<>(); - String dataSource = config.get("dataSource"); - - if (dataSource != null) { - properties.put(AvailableSettings.JAKARTA_NON_JTA_DATASOURCE, dataSource); - } else { - properties.put(AvailableSettings.JAKARTA_JDBC_URL, config.get("url")); - properties.put(AvailableSettings.JAKARTA_JDBC_DRIVER, config.get("driver")); - - String user = config.get("user"); - if (user != null) { - properties.put(AvailableSettings.JAKARTA_JDBC_USER, user); - } - String password = config.get("password"); - if (password != null) { - properties.put(AvailableSettings.JAKARTA_JDBC_PASSWORD, password); - } - } - - String schema = getSchema(); - if (schema != null) { - properties.put(HIBERNATE_DEFAULT_SCHEMA, schema); - } - - properties.put("hibernate.show_sql", config.getBoolean("showSql", false)); - properties.put("hibernate.format_sql", config.getBoolean("formatSql", true)); - properties.put("hibernate.dialect", config.get("driverDialect")); - // metadata contributor to register the json type - properties.put("hibernate.metadata_builder_contributor", "org.keycloak.models.map.storage.jpa.hibernate.contributor.JsonbMetadataBuilderContributor"); - properties.put(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name()); - Long lockTimeout = config.getLong("lockTimeout", DEFAULT_LOCK_TIMEOUT); - if (lockTimeout >= 0) { - // This property does not work for PostgreSQL/CockroachDB - https://hibernate.atlassian.net/browse/HHH-16181 - properties.put(AvailableSettings.JAKARTA_LOCK_TIMEOUT, String.valueOf(lockTimeout)); - } else { - logger.warnf("Database %s used without lockTimeout option configured. This can result in deadlock where one connection waits for a pessimistic write lock forever.", databaseShortName); - } - - logger.trace("Creating EntityManagerFactory"); - ParsedPersistenceXmlDescriptor descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit( - JpaMapStorageProviderFactory.class.getClassLoader() - .getResource("default-map-jpa-persistence.xml")); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder(descriptor, properties).build(); - logger.trace("EntityManagerFactory created"); - - return emf; - } - - private String getSchema() { - String schema = config.get("schema"); - if (schema != null && schema.contains("-") && ! Boolean.parseBoolean(System.getProperty(GlobalConfiguration.PRESERVE_SCHEMA_CASE.getKey()))) { - System.setProperty(GlobalConfiguration.PRESERVE_SCHEMA_CASE.getKey(), "true"); - logger.warnf("The passed schema '%s' contains a dash. Setting liquibase config option PRESERVE_SCHEMA_CASE to true. See https://github.com/keycloak/keycloak/issues/20870 for more information.", schema); - } - return schema; - } - - protected EntityManagerFactory getEntityManagerFactory() { - return emf; - } - - public void validateAndUpdateSchema(KeycloakSession session, Class modelType) { - /* - For authz - there is validation run 5 times. For each authz model class separately. - There is single changlelog "jpa-authz-changelog.xml" used. - Possible optimization would be to cache: Set validatedModelNames instead of classes. Something like: - - String modelName = ModelEntityUtil.getModelName(modelType); - if (modelName == null) { - throw new IllegalStateException("Cannot find changlelog for modelClass " + modelType.getName()); - } - - modelName = modelName.startsWith("authz-") ? "authz" : modelName; - - if (this.validatedModelNames.add(modelName)) { - */ - if (!this.validatedModels.contains(modelType)) { - synchronized (SYNC_MODELS.computeIfAbsent(modelType, mc -> new Object())) { - if (!this.validatedModels.contains(modelType)) { - Transaction suspended = null; - try { - if (jtaEnabled) { - suspended = jtaLookup.getTransactionManager().suspend(); - jtaLookup.getTransactionManager().begin(); - } - - Connection connection = getConnection(); - try { - if (logger.isDebugEnabled()) printOperationalInfo(connection); - - MapJpaUpdaterProvider updater = session.getProvider(MapJpaUpdaterProvider.class); - MapJpaUpdaterProvider.Status status = updater.validate(modelType, connection, getSchema()); - databaseShortName = updater.getDatabaseShortName(); - - if (!status.equals(VALID)) { - update(modelType, connection, session); - } - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - logger.warn("Can't close connection", e); - } - } - } - - if (jtaEnabled) { - jtaLookup.getTransactionManager().commit(); - } - } catch (SystemException | NotSupportedException | RollbackException | HeuristicMixedException | - HeuristicRollbackException e) { - if (jtaEnabled) { - try { - jtaLookup.getTransactionManager().rollback(); - } catch (SystemException ex) { - logger.error("Unable to roll back JTA transaction, e"); - } - } - throw new RuntimeException(e); - } finally { - if (suspended != null) { - try { - jtaLookup.getTransactionManager().resume(suspended); - } catch (InvalidTransactionException | SystemException e) { - throw new RuntimeException(e); - } - } - } - validatedModels.add(modelType); - } - } - } - } - - protected Connection getConnection() { - try { - String dataSourceLookup = config.get("dataSource"); - if (dataSourceLookup != null) { - DataSource dataSource = (DataSource) new InitialContext().lookup(dataSourceLookup); - return dataSource.getConnection(); - } else { - Class.forName(config.get("driver")); - return DriverManager.getConnection( - StringPropertyReplacer.replaceProperties(config.get("url"), System.getProperties()), - config.get("user"), - config.get("password")); - } - } catch (ClassNotFoundException | SQLException | NamingException e) { - throw new RuntimeException("Failed to connect to database", e); - } - } - - private void printOperationalInfo(Connection connection) { - try { - HashMap operationalInfo = new LinkedHashMap<>(); - DatabaseMetaData md = connection.getMetaData(); - operationalInfo.put("databaseUrl", md.getURL()); - operationalInfo.put("databaseUser", md.getUserName()); - operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion()); - operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion()); - - logger.debugf("Database info: %s", operationalInfo.toString()); - } catch (SQLException e) { - logger.warn("Unable to prepare operational info due database exception: " + e.getMessage()); - } - } - - private void update(Class modelType, Connection connection, KeycloakSession session) { - if (modelType == MapLockEntity.class) { - // as the MapLockEntity is used by the MapGlobalLockProvider itself, don't create a global lock for creating that schema - session.getProvider(MapJpaUpdaterProvider.class).update(modelType, connection, getSchema()); - } else { - session.getProvider(GlobalLockProvider.class).withLock(modelType.getName(), lockedSession -> { - lockedSession.getProvider(MapJpaUpdaterProvider.class).update(modelType, connection, getSchema()); - return null; - }); - } - } - - @Override - public List getConfigMetadata() { - return ProviderConfigurationBuilder.create() - .property() - .name("lockTimeout") - .type("long") - .defaultValue(10000L) - .helpText("The maximum time to wait in milliseconds when waiting for acquiring a pessimistic read lock. If set to negative there is no timeout configured.") - .add().build(); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapUtils.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapUtils.java deleted file mode 100644 index b788d3856c7..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapUtils.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2016 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.storage.jpa; - -import org.hibernate.Session; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.jboss.logging.Logger; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Map; -import java.util.Properties; -import java.util.regex.Pattern; - -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.HIBERNATE_DEFAULT_SCHEMA; - -/** - * @author Marek Posolda - */ -public class JpaMapUtils { - - public static final String QUERY_NATIVE_SUFFIX = "[native]"; - public static final String QUERY_JPQL_SUFFIX = "[jpql]"; - private static final Logger logger = Logger.getLogger(JpaMapUtils.class); - - public static String getSchemaForNativeQuery(EntityManager em) { - String schema = (String) em.getEntityManagerFactory().getProperties().get(HIBERNATE_DEFAULT_SCHEMA); - return (schema == null) ? "" : "\"" + schema + "\"."; - } - - /** - * Method that adds the different query variants for the database. - * The method loads the queries specified in the files - * META-INF/jpa-map/queries-{dbType}.properties and the default - * META-INF/jpa-map/queries-default.properties. At least the default file - * should exist inside the jar file. The default file contains all the - * needed queries and the specific one can overload all or some of them for - * that database type. - * @param databaseType The database type as returned by getDatabaseType - */ - public static Properties loadSpecificNamedQueries(String databaseType) { - URL specificUrl = JpaMapUtils.class.getClassLoader().getResource("META-INF/jpa-map/queries-" + databaseType + ".properties"); - URL defaultUrl = JpaMapUtils.class.getClassLoader().getResource("META-INF/jpa-map/queries-default.properties"); - - if (defaultUrl == null) { - throw new IllegalStateException("META-INF/jpa-map/queries-default.properties was not found in the classpath"); - } - - Properties specificQueries = loadSqlProperties(specificUrl); - Properties defaultQueries = loadSqlProperties(defaultUrl); - Properties queries = new Properties(); - - for (String queryNameFull : defaultQueries.stringPropertyNames()) { - String querySql = defaultQueries.getProperty(queryNameFull); - String queryName = getQueryShortName(queryNameFull); - String specificQueryNameFull = getQueryFromProperties(queryName, specificQueries); - - if (specificQueryNameFull != null) { - // the query is redefined in the specific database file => use it - queryNameFull = specificQueryNameFull; - querySql = specificQueries.getProperty(queryNameFull); - } - - queries.put(queryNameFull, querySql); - } - - return queries; - } - - /** - * Returns the name of the query in the queries file. It searches for the - * three possible forms: name[native], name[jpql] or name. - * @param name The name of the query to search - * @param queries The properties file with the queries - * @return The key with the query found or null if not found - */ - private static String getQueryFromProperties(String name, Properties queries) { - if (queries == null) { - return null; - } - String nameFull = name + QUERY_NATIVE_SUFFIX; - if (queries.containsKey(nameFull)) { - return nameFull; - } - nameFull = name + QUERY_JPQL_SUFFIX; - if (queries.containsKey(nameFull)) { - return nameFull; - } - nameFull = name; - if (queries.containsKey(nameFull)) { - return nameFull; - } - return null; - } - - /** - * Loads the URL as a properties file. - * @param url The url to load, it can be null - * @return A properties file with the url loaded or null - */ - public static Properties loadSqlProperties(URL url) { - if (url == null) { - return null; - } - Properties props = new Properties(); - try (InputStream is = url.openStream()) { - props.load(is); - } catch (IOException e) { - throw new IllegalStateException(e); - } - return props; - } - - - /** - * Configures a named query to Hibernate. - * - * @param queryName the query name - * @param querySql the query SQL - * @param entityManager the entity manager - */ - public static void configureNamedQuery(String queryName, String querySql, EntityManager entityManager) { - boolean isNative = queryName.endsWith(QUERY_NATIVE_SUFFIX); - queryName = getQueryShortName(queryName); - - logger.tracef("adding query from properties files native=%b %s:%s", isNative, queryName, querySql); - - SessionFactoryImplementor sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactoryImplementor.class); - - if (isNative) { - sessionFactory.addNamedQuery(queryName, entityManager.createNativeQuery(querySql)); - } else { - sessionFactory.addNamedQuery(queryName, entityManager.createQuery(querySql)); - } - } - - /** - * Returns the query name but removing the suffix. - * @param name The query name as it is on the key - * @return The name without the suffix - */ - private static String getQueryShortName(String name) { - if (name.endsWith(QUERY_NATIVE_SUFFIX)) { - return name.substring(0, name.length() - QUERY_NATIVE_SUFFIX.length()); - } else if (name.endsWith(QUERY_JPQL_SUFFIX)) { - return name.substring(0, name.length() - QUERY_JPQL_SUFFIX.length()); - } else { - return name; - } - } - - public static String getDatabaseType(String productName) { - switch (productName) { - case "Microsoft SQL Server": - case "SQLOLEDB": - return "mssql"; - case "EnterpriseDB": - return "postgresql"; - default: - return productName.toLowerCase(); - } - } - - - public static void addSpecificNamedQueries(EntityManagerFactory emf) { - EntityManager em = null; - try { - em = emf.createEntityManager(); - String dbProductName = em.unwrap(Session.class).doReturningWork(connection -> connection.getMetaData().getDatabaseProductName()); - String dbKind = getDatabaseType(dbProductName); - String schemaForNativeQuery = getSchemaForNativeQuery(em); - for (Map.Entry query : loadSpecificNamedQueries(dbKind.toLowerCase()).entrySet()) { - String queryName = query.getKey().toString(); - String querySql = query.getValue().toString(); - querySql = querySql.replaceAll(Pattern.quote("${schemaprefix}"), schemaForNativeQuery); - configureNamedQuery(queryName, querySql, em); - } - } finally { - if (em != null) { - em.close(); - } - } - } - - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java deleted file mode 100644 index 681113692d4..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import com.fasterxml.jackson.core.JsonProcessingException; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.Predicate; - -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.storage.SearchableModelField; - -/** - * Abstract class containing methods common to all Jpa*ModelCriteriaBuilder implementations - * - * @param Entity - * @param Model - * @param specific implementation of this class - */ -public abstract class JpaModelCriteriaBuilder> implements ModelCriteriaBuilder { - - private final Function, Self> instantiator; - private JpaPredicateFunction predicateFunc = null; - private boolean isDistinct = false; - - public JpaModelCriteriaBuilder(Function, Self> instantiator) { - this.instantiator = instantiator; - } - - public JpaModelCriteriaBuilder(Function, Self> instantiator, - JpaPredicateFunction predicateFunc) { - this.instantiator = instantiator; - this.predicateFunc = predicateFunc; - } - - public JpaModelCriteriaBuilder(Function, Self> instantiator, - JpaPredicateFunction predicateFunc, - boolean isDistinct) { - this.instantiator = instantiator; - this.predicateFunc = predicateFunc; - this.isDistinct = isDistinct; - } - - protected void validateValue(Object[] value, SearchableModelField field, ModelCriteriaBuilder.Operator op, Class... expectedTypes) { - if (value == null || expectedTypes == null || value.length != expectedTypes.length) { - throw new CriterionNotSupportedException(field, op, "Invalid argument: " + Arrays.toString(value)); - } - for (int i = 0; i < expectedTypes.length; i++) { - if (! expectedTypes[i].isInstance(value[i])) { - throw new CriterionNotSupportedException(field, op, "Expected types: " + Arrays.toString(expectedTypes) + - " but got: " + Arrays.toString(value)); - } - } - } - - protected String convertToJson(Object input) { - try { - return JsonbType.MAPPER.writeValueAsString(input); - } catch (JsonProcessingException ex) { - throw new RuntimeException("Unable to write value as String.", ex); - } - } - - @SafeVarargs - @Override - public final Self and(Self... builders) { - return instantiator.apply((cb, query, root) -> cb.and(Stream.of(builders).map((Self b) -> b.getPredicateFunc().apply(cb, query, root)).toArray(Predicate[]::new))); - } - - @SafeVarargs - @Override - public final Self or(Self... builders) { - return instantiator.apply((cb, query, root) -> cb.or(Stream.of(builders).map((Self b) -> (b).getPredicateFunc().apply(cb, query, root)).toArray(Predicate[]::new))); - } - - @Override - public Self not(Self builder) { - return instantiator.apply((cb, query, root) -> cb.not(builder.getPredicateFunc().apply(cb, query, root))); - } - - public JpaPredicateFunction getPredicateFunc() { - return predicateFunc; - } - - public boolean isDistinct() { - return this.isDistinct; - } - - @SuppressWarnings("unchecked") - protected Collection getValuesForInOperator(Object[] values, SearchableModelField modelField) { - if (values == null || values.length == 0) throw new CriterionNotSupportedException(modelField, Operator.IN); - - Collection collectionValues; - if (values.length == 1) { - - if (values[0] instanceof Object[]) { - collectionValues = Arrays.asList(values[0]); - } else if (values[0] instanceof Collection) { - collectionValues = (Collection) values[0]; - } else if (values[0] instanceof Stream) { - try (Stream str = ((Stream) values[0])) { - collectionValues = str.collect(Collectors.toCollection(ArrayList::new)); - } - } else { - collectionValues = Collections.singleton(values[0]); - } - - } else { - collectionValues = new HashSet(Arrays.asList(values)); - } - return collectionValues; - } - - protected Set getUuidsForInOperator(Object[] values, SearchableModelField modelField) { - return getValuesForInOperator(values, modelField).stream() - .map(val -> StringKeyConverter.UUIDKey.INSTANCE.fromStringSafe(Objects.toString(val, null))) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - protected Predicate hashExpression(CriteriaBuilder cb, Join join, String columnName, Object value) { - return cb.equal(join.get(columnName), cb.function("kc_hash", Object.class, cb.literal(value))); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaPredicateFunction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaPredicateFunction.java deleted file mode 100644 index 7fd5eb504e9..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaPredicateFunction.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import java.util.Objects; -import java.util.function.Function; - -/** - * @author Alexander Schwartz - */ -@FunctionalInterface -public interface JpaPredicateFunction { - Predicate apply(CriteriaBuilder t, JpaSubqueryProvider u, Root v); - - default JpaPredicateFunction andThen(Function after) { - Objects.requireNonNull(after); - return (CriteriaBuilder t, JpaSubqueryProvider u, Root v) -> after.apply(apply(t, u, v)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaRootEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaRootEntity.java deleted file mode 100644 index 643ee74c9a3..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaRootEntity.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import java.io.Serializable; -import java.util.Objects; - -import org.keycloak.models.map.common.AbstractEntity; - -/** - * Interface for all root entities in the JPA storage. - */ -public interface JpaRootEntity extends AbstractEntity, Serializable { - - /** - * @return current supported version of the JPA entity used for schema versioning. - */ - Integer getEntityVersion(); - - /** - * @param entityVersion sets current supported version to JPA entity. - */ - void setEntityVersion(Integer entityVersion); - - /** - * In case of any update on entity, we want to update the entityVersion - * to current one. - * This includes downgrading from a future version of Keycloak, as the entityVersion must match the JSON - * and the additional tables this version writes. - * - * The listener {@link org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaEntityVersionListener} - * calls this method whenever the root entity or one of its children changes. - * - * Future versions of this method might restrict downgrading to downgrade only from the next version. - * - * @return true if the entityVersion was effectively changed, false otherwise. - */ - default boolean updateEntityVersion() { - Integer ev = getEntityVersion(); - Integer currentEv = getCurrentSchemaVersion(); - if (ev != null && !Objects.equals(ev, currentEv)) { - setEntityVersion(currentEv); - return true; - } - return false; - } - - Integer getCurrentSchemaVersion(); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaRootVersionedEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaRootVersionedEntity.java deleted file mode 100644 index 34fab6fb436..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaRootVersionedEntity.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -/** - * Interface for all root entities which implements optimistic locking. - */ -public interface JpaRootVersionedEntity extends JpaRootEntity { - - /** - * Version of the JPA entity used for optimistic locking - */ - int getVersion(); - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaSubqueryProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaSubqueryProvider.java deleted file mode 100644 index 50704a758e8..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaSubqueryProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import jakarta.persistence.criteria.Subquery; - -/** - * This is handed down to a {@link JpaModelCriteriaBuilder} to be able to create subqueries. - * Depending on the caller this will delegate of an instance of a {@link jakarta.persistence.criteria.CriteriaDelete} - * or a {@link jakarta.persistence.criteria.CriteriaQuery} as necessary. - * - * @author Alexander Schwartz - */ -@FunctionalInterface -public interface JpaSubqueryProvider { - Subquery subquery(Class type); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java deleted file mode 100644 index 4c537d2e8cd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa; - -import jakarta.persistence.EntityTransaction; -import jakarta.persistence.PersistenceException; - -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakTransaction; - -/** - * Wraps an {@link EntityTransaction} as a {@link KeycloakTransaction} so it can be enlisted in {@link org.keycloak.models.KeycloakTransactionManager}. - * - * @author Stefan Guilhen - */ -public class JpaTransactionWrapper implements KeycloakTransaction { - - private static final Logger logger = Logger.getLogger(JpaTransactionWrapper.class); - - private final EntityTransaction transaction; - - public JpaTransactionWrapper(EntityTransaction transaction) { - this.transaction = transaction; - } - - @Override - public void begin() { - logger.tracef("tx %d: begin", hashCode()); - this.transaction.begin(); - } - - @Override - public void commit() { - try { - logger.tracef("tx %d: commit", hashCode()); - this.transaction.commit(); - } catch(PersistenceException pe) { - throw PersistenceExceptionConverter.convert(pe.getCause() != null ? pe.getCause() : pe); - } - } - - @Override - public void rollback() { - logger.tracef("tx %d: rollback", hashCode()); - this.transaction.rollback(); - } - - @Override - public void setRollbackOnly() { - this.transaction.setRollbackOnly(); - } - - @Override - public boolean getRollbackOnly() { - return this.transaction.getRollbackOnly(); - } - - @Override - public boolean isActive() { - return this.transaction.isActive(); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PaginationUtils.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PaginationUtils.java deleted file mode 100644 index f164ab2aea6..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PaginationUtils.java +++ /dev/null @@ -1,43 +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.storage.jpa; - -import jakarta.persistence.TypedQuery; - -public class PaginationUtils { - - public static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE >> 1; - - public static TypedQuery paginateQuery(TypedQuery query, Integer first, Integer max) { - if (first != null && first > 0) { - query = query.setFirstResult(first); - - // Workaround for https://hibernate.atlassian.net/browse/HHH-14295 - if (max == null || max < 0) { - max = DEFAULT_MAX_RESULTS; - } - } - - if (max != null && max >= 0) { - query = query.setMaxResults(max); - } - - return query; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PersistenceExceptionConverter.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PersistenceExceptionConverter.java deleted file mode 100644 index 70fb2870291..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PersistenceExceptionConverter.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2016 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.storage.jpa; - -import org.hibernate.exception.ConstraintViolationException; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.ModelException; - -import jakarta.persistence.EntityExistsException; -import jakarta.persistence.EntityManager; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.regex.Pattern; - -/** - * @author Stian Thorgersen - */ -public class PersistenceExceptionConverter implements InvocationHandler { - - private static final Pattern WRITE_METHOD_NAMES = Pattern.compile("persist|merge"); - - private final EntityManager em; - private final boolean batchEnabled; - private final int batchSize; - private int changeCount = 0; - - public static EntityManager create(KeycloakSession session, EntityManager em) { - return (EntityManager) Proxy.newProxyInstance(EntityManager.class.getClassLoader(), new Class[]{EntityManager.class}, new PersistenceExceptionConverter(session, em)); - } - - private PersistenceExceptionConverter(KeycloakSession session, EntityManager em) { - batchEnabled = session.getAttributeOrDefault(Constants.STORAGE_BATCH_ENABLED, false); - batchSize = session.getAttributeOrDefault(Constants.STORAGE_BATCH_SIZE, 100); - this.em = em; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - try { - flushInBatchIfEnabled(method); - return method.invoke(em, args); - } catch (InvocationTargetException e) { - throw convert(e.getCause()); - } - } - - private void flushInBatchIfEnabled(Method method) { - if (batchEnabled) { - if (WRITE_METHOD_NAMES.matcher(method.getName()).matches()) { - if (changeCount++ > batchSize) { - em.flush(); - em.clear(); - changeCount = 0; - } - } - } - } - - public static ModelException convert(Throwable t) { - if (t.getCause() != null && t.getCause() instanceof ConstraintViolationException) { - throw new ModelDuplicateException(t); - } if (t instanceof EntityExistsException || t instanceof ConstraintViolationException) { - throw new ModelDuplicateException(t); - } else { - throw new ModelException(t); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapStorage.java deleted file mode 100644 index 92529c5bf1e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapStorage.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityDelegate; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION; - -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.authSession.delegate.JpaRootAuthenticationSessionDelegateProvider; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity; -import org.keycloak.sessions.RootAuthenticationSessionModel; - -public class JpaRootAuthenticationSessionMapStorage extends JpaMapStorage { - - public JpaRootAuthenticationSessionMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class, em); - } - - @Override - public Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaRootAuthenticationSessionEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("timestamp"), - root.get("expiration") - ); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_AUTH_SESSION); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaRootAuthenticationSessionModelCriteriaBuilder(); - } - - @Override - protected MapRootAuthenticationSessionEntity mapToEntityDelegate(JpaRootAuthenticationSessionEntity original) { - return new MapRootAuthenticationSessionEntityDelegate(new JpaRootAuthenticationSessionDelegateProvider(original, em)); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java deleted file mode 100644 index be7be64fe4f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession; - -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity; -import org.keycloak.sessions.RootAuthenticationSessionModel; -import org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields; -import org.keycloak.storage.SearchableModelField; - -public class JpaRootAuthenticationSessionModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaRootAuthenticationSessionModelCriteriaBuilder() { - super(JpaRootAuthenticationSessionModelCriteriaBuilder::new); - } - - private JpaRootAuthenticationSessionModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaRootAuthenticationSessionModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaRootAuthenticationSessionModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.REALM_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaRootAuthenticationSessionModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/delegate/JpaRootAuthenticationSessionDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/delegate/JpaRootAuthenticationSessionDelegateProvider.java deleted file mode 100644 index 9d896d37bfb..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/delegate/JpaRootAuthenticationSessionDelegateProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession.delegate; - -import java.util.UUID; -import jakarta.persistence.EntityManager; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity; - -public class JpaRootAuthenticationSessionDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaRootAuthenticationSessionDelegateProvider(JpaRootAuthenticationSessionEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapRootAuthenticationSessionEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapRootAuthenticationSessionEntityFields) { - switch ((MapRootAuthenticationSessionEntityFields) field) { - case ID: - case REALM_ID: - case TIMESTAMP: - case EXPIRATION: - return getDelegate(); - - default: - setDelegate(em.find(JpaRootAuthenticationSessionEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid client field: " + field); - } - } else { - setDelegate(em.find(JpaRootAuthenticationSessionEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java deleted file mode 100644 index 80ca83db8f2..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession.entity; - -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION; -import static org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionEntity.TABLE_NAME; - -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.sessions.CommonClientSessionModel; - -/** - * Entity represents individual authentication session. - */ -@Entity -@Table(name = TABLE_NAME) -public class JpaAuthenticationSessionEntity extends UpdatableEntity.Impl implements MapAuthenticationSessionEntity, JpaRootVersionedEntity, JpaChildEntity{ - - public static final String TABLE_NAME = "kc_auth_session"; - @Id - @Column - @GeneratedValue - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaAuthenticationSessionMetadata metadata; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="fk_root") - private JpaRootAuthenticationSessionEntity root; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaAuthenticationSessionEntity() { - this.metadata = new JpaAuthenticationSessionMetadata(); - } - - public JpaAuthenticationSessionEntity(DeepCloner cloner) { - this.metadata = new JpaAuthenticationSessionMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public JpaRootAuthenticationSessionEntity getParent() { - return root; - } - - public void setParent(JpaRootAuthenticationSessionEntity root) { - this.root = root; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - this.id = id == null ? null : UUID.fromString(id); - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer version) { - metadata.setEntityVersion(version); - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_AUTH_SESSION; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getTabId() { - return metadata.getTabId(); - } - - @Override - public void setTabId(String tabId) { - metadata.setTabId(tabId); - } - - @Override - public Map getUserSessionNotes() { - return metadata.getUserSessionNotes(); - } - - @Override - public void setUserSessionNotes(Map userSessionNotes) { - metadata.setUserSessionNotes(userSessionNotes); - } - - @Override - public void setUserSessionNote(String name, String value) { - metadata.setUserSessionNote(name, value); - } - - @Override - public String getClientUUID() { - return metadata.getClientUUID(); - } - - @Override - public void setClientUUID(String clientUUID) { - metadata.setClientUUID(clientUUID); - } - - @Override - public String getAuthUserId() { - return metadata.getAuthUserId(); - } - - @Override - public void setAuthUserId(String authUserId) { - metadata.setAuthUserId(authUserId); - } - - @Override - public Long getTimestamp() { - return metadata.getTimestamp(); - } - - @Override - public void setTimestamp(Long timestamp) { - metadata.setTimestamp(timestamp); - } - - @Override - public String getRedirectUri() { - return metadata.getRedirectUri(); - } - - @Override - public void setRedirectUri(String redirectUri) { - metadata.setRedirectUri(redirectUri); - } - - @Override - public String getAction() { - return metadata.getAction(); - } - - @Override - public void setAction(String action) { - metadata.setAction(action); - } - - @Override - public Set getClientScopes() { - return metadata.getClientScopes(); - } - - @Override - public void setClientScopes(Set clientScopes) { - metadata.setClientScopes(clientScopes); - } - - @Override - public Set getRequiredActions() { - return metadata.getRequiredActions(); - } - - @Override - public void setRequiredActions(Set requiredActions) { - metadata.setRequiredActions(requiredActions); - } - - @Override - public void addRequiredAction(String requiredAction) { - metadata.addRequiredAction(requiredAction); - } - - @Override - public void removeRequiredAction(String action) { - metadata.removeRequiredAction(action); - } - - @Override - public String getProtocol() { - return metadata.getProtocol(); - } - - @Override - public void setProtocol(String protocol) { - metadata.setProtocol(protocol); - } - - @Override - public Map getClientNotes() { - return metadata.getClientNotes(); - } - - @Override - public void setClientNotes(Map clientNotes) { - metadata.setClientNotes(clientNotes); - } - - @Override - public void setClientNote(String name, String value) { - metadata.setClientNote(name, value); - } - - @Override - public void removeClientNote(String name) { - metadata.removeClientNote(name); - } - - @Override - public Map getAuthNotes() { - return metadata.getAuthNotes(); - } - - @Override - public void setAuthNotes(Map authNotes) { - metadata.setAuthNotes(authNotes); - } - - @Override - public void setAuthNote(String name, String value) { - metadata.setAuthNote(name, value); - } - - @Override - public void removeAuthNote(String name) { - metadata.removeAuthNote(name); - } - - @Override - public Map getExecutionStatuses() { - return metadata.getExecutionStatuses(); - } - - @Override - public void setExecutionStatuses(Map executionStatus) { - metadata.setExecutionStatuses(executionStatus); - } - - @Override - public void setExecutionStatus(String authenticator, CommonClientSessionModel.ExecutionStatus status) { - metadata.setExecutionStatus(authenticator, status); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaAuthenticationSessionEntity)) return false; - return Objects.equals(getId(), ((JpaAuthenticationSessionEntity) obj).getId()) && - Objects.equals(getTabId(), ((JpaAuthenticationSessionEntity) obj).getTabId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionMetadata.java deleted file mode 100644 index ac9eeb582df..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaAuthenticationSessionMetadata extends MapAuthenticationSessionEntityImpl implements Serializable { - - public JpaAuthenticationSessionMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaAuthenticationSessionMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaRootAuthenticationSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaRootAuthenticationSessionEntity.java deleted file mode 100644 index f53fe49bfdd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaRootAuthenticationSessionEntity.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession.entity; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity.AbstractRootAuthenticationSessionEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER; -import static org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity.TABLE_NAME; - -/** - * Entity represents root authentication session. - * - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = TABLE_NAME) -public class JpaRootAuthenticationSessionEntity extends AbstractRootAuthenticationSessionEntity implements JpaRootVersionedEntity { - - public static final String TABLE_NAME = "kc_auth_root_session"; - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaRootAuthenticationSessionMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long timestamp; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long expiration; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set authSessions = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaRootAuthenticationSessionEntity() { - this.metadata = new JpaRootAuthenticationSessionMetadata(); - } - - public JpaRootAuthenticationSessionEntity(DeepCloner cloner) { - this.metadata = new JpaRootAuthenticationSessionMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select root auth session without metadata(json) field. - */ - public JpaRootAuthenticationSessionEntity(UUID id, int version, Integer entityVersion, String realmId, Long timestamp, Long expiration) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.timestamp = timestamp; - this.expiration = expiration; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_ROOT_AUTH_SESSION; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public Long getTimestamp() { - if (isMetadataInitialized()) return metadata.getTimestamp(); - return timestamp; - } - - @Override - public void setTimestamp(Long timestamp) { - metadata.setTimestamp(timestamp); - } - - @Override - public Long getExpiration() { - if (isMetadataInitialized()) return metadata.getExpiration(); - return expiration; - } - - @Override - public void setExpiration(Long expiration) { - metadata.setExpiration(expiration); - } - - @Override - public Set getAuthenticationSessions() { - return authSessions.stream().map(MapAuthenticationSessionEntity.class::cast).collect(Collectors.toSet()); - } - - @Override - public void setAuthenticationSessions(Set authenticationSessions) { - authSessions.clear(); - if (authenticationSessions != null) { - for (MapAuthenticationSessionEntity authenticationSession : authenticationSessions) { - addAuthenticationSession(authenticationSession); - } - } - } - - @Override - public void addAuthenticationSession(MapAuthenticationSessionEntity authenticationSession) { - JpaAuthenticationSessionEntity jpaAuthSession = JpaAuthenticationSessionEntity.class.cast(CLONER.from(authenticationSession)); - jpaAuthSession.setParent(this); - jpaAuthSession.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION); - authSessions.add(jpaAuthSession); - } - - @Override - public Optional getAuthenticationSession(String tabId) { - return authSessions.stream().filter(as -> Objects.equals(as.getTabId(), tabId)).findFirst().map(MapAuthenticationSessionEntity.class::cast); - } - - @Override - public Boolean removeAuthenticationSession(String tabId) { - return authSessions.removeIf(as -> Objects.equals(as.getTabId(), tabId)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaRootAuthenticationSessionEntity)) return false; - return Objects.equals(getId(), ((JpaRootAuthenticationSessionEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaRootAuthenticationSessionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaRootAuthenticationSessionMetadata.java deleted file mode 100644 index f0c992dc62b..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaRootAuthenticationSessionMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authSession.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaRootAuthenticationSessionMetadata extends MapRootAuthenticationSessionEntityImpl implements Serializable { - - public JpaRootAuthenticationSessionMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaRootAuthenticationSessionMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapStorage.java deleted file mode 100644 index 15a06c42d4d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapStorage.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.permission; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntityDelegate; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.authorization.permission.delegate.JpaPermissionDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity; - -public class JpaPermissionMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaPermissionMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaPermissionEntity.class, PermissionTicket.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaPermissionEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("resourceServerId"), - root.get("owner"), - root.get("scopeId"), - root.get("policyId"), - root.get("requester"), - root.get("resourceId")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaPermissionModelCriteriaBuilder(); - } - - @Override - protected MapPermissionTicketEntity mapToEntityDelegate(JpaPermissionEntity original) { - return new MapPermissionTicketEntityDelegate(new JpaPermissionDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java deleted file mode 100644 index e762151b8da..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.permission; - -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import jakarta.persistence.criteria.CriteriaBuilder.In; - -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.PermissionTicket.SearchableFields; -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.storage.SearchableModelField; - -public class JpaPermissionModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaPermissionModelCriteriaBuilder() { - super(JpaPermissionModelCriteriaBuilder::new); - } - - private JpaPermissionModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaPermissionModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaPermissionModelCriteriaBuilder compare(SearchableModelField modelField, ModelCriteriaBuilder.Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.ID || - modelField == SearchableFields.SCOPE_ID || - modelField == SearchableFields.POLICY_ID || - modelField == SearchableFields.RESOURCE_ID || - modelField == SearchableFields.RESOURCE_SERVER_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.get(modelField.getName()), uuid); - }); - } else if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.OWNER || - modelField == SearchableFields.REQUESTER) { - - validateValue(value, modelField, op, String.class); - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case EXISTS: - if (modelField == SearchableFields.SCOPE_ID || - modelField == SearchableFields.REQUESTER) { - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> - cb.isNotNull(root.get(modelField.getName())) - ); - } else if (modelField == SearchableFields.GRANTED_TIMESTAMP) { - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> - cb.isNotNull(cb.function("->", JsonbType.class, root.get("metadata"), cb.literal("fGrantedTimestamp"))) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case NOT_EXISTS: - if (modelField == SearchableFields.SCOPE_ID || - modelField == SearchableFields.REQUESTER) { - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> - cb.isNull(root.get(modelField.getName())) - ); - } else if (modelField == SearchableFields.GRANTED_TIMESTAMP) { - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> - cb.isNull(cb.function("->", JsonbType.class, root.get("metadata"), cb.literal("fGrantedTimestamp"))) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case IN: - if (modelField == SearchableFields.RESOURCE_ID) { - - Set uuids = getUuidsForInOperator(value, modelField); - - if (uuids.isEmpty()) return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaPermissionModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get(modelField.getName())); - uuids.forEach(in::value); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/delegate/JpaPermissionDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/delegate/JpaPermissionDelegateProvider.java deleted file mode 100644 index b3c9c3b328a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/delegate/JpaPermissionDelegateProvider.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.permission.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; - -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity; - -public class JpaPermissionDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaPermissionDelegateProvider(JpaPermissionEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapPermissionTicketEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapPermissionTicketEntityFields) { - switch ((MapPermissionTicketEntityFields) field) { - case ID: - case OWNER: - case SCOPE_ID: - case REALM_ID: - case POLICY_ID: - case REQUESTER: - case RESOURCE_ID: - case RESOURCE_SERVER_ID: - return getDelegate(); - - default: - setDelegate(em.find(JpaPermissionEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid resource field: " + field); - } - } else { - setDelegate(em.find(JpaPermissionEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/entity/JpaPermissionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/entity/JpaPermissionEntity.java deleted file mode 100644 index 1f6c7f6dfbc..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/entity/JpaPermissionEntity.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.permission.entity; - -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity.AbstractMapPermissionTicketEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_authz_permission", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "resourceServerId", "scopeId", "resourceId", "owner", "requester"})}) -public class JpaPermissionEntity extends AbstractMapPermissionTicketEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaPermissionMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String owner; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String requester; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID resourceServerId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID scopeId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID policyId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID resourceId; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaPermissionEntity() { - this.metadata = new JpaPermissionMetadata(); - } - - public JpaPermissionEntity(DeepCloner cloner) { - this.metadata = new JpaPermissionMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select object without metadata(json) field. - */ - public JpaPermissionEntity(UUID id, int version, Integer entityVersion, String realmId, - UUID resourceServerId, String owner, UUID scopeId, UUID policyId, - String requester, UUID resourceId) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.resourceServerId = resourceServerId; - this.owner = owner; - this.scopeId = scopeId; - this.policyId = policyId; - this.requester = requester; - this.resourceId = resourceId; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getOwner() { - if (isMetadataInitialized()) return metadata.getOwner(); - return owner; - } - - @Override - public void setOwner(String owner) { - metadata.setOwner(owner); - } - - @Override - public String getRequester() { - if (isMetadataInitialized()) return metadata.getRequester(); - return requester; - } - - @Override - public void setRequester(String requester) { - metadata.setRequester(requester); - } - - @Override - public Long getCreatedTimestamp() { - return metadata.getCreatedTimestamp(); - } - - @Override - public void setCreatedTimestamp(Long createdTimestamp) { - metadata.setCreatedTimestamp(createdTimestamp); - } - - @Override - public Long getGrantedTimestamp() { - return metadata.getGrantedTimestamp(); - } - - @Override - public void setGrantedTimestamp(Long grantedTimestamp) { - metadata.setGrantedTimestamp(grantedTimestamp); - } - - @Override - public String getResourceId() { - if (isMetadataInitialized()) return metadata.getResourceId(); - return resourceId == null ? null : resourceId.toString(); - } - - @Override - public void setResourceId(String resourceId) { - metadata.setResourceId(resourceId); - } - - @Override - public String getScopeId() { - if (isMetadataInitialized()) return metadata.getScopeId(); - return scopeId == null ? null: scopeId.toString(); - } - - @Override - public void setScopeId(String scopeId) { - metadata.setScopeId(scopeId); - } - - @Override - public String getResourceServerId() { - if (isMetadataInitialized()) return metadata.getResourceServerId(); - return resourceServerId == null ? null : resourceServerId.toString(); - } - - @Override - public void setResourceServerId(String resourceServerId) { - metadata.setResourceServerId(resourceServerId); - } - - @Override - public String getPolicyId() { - if (isMetadataInitialized()) return metadata.getPolicyId(); - return policyId == null ? null : policyId.toString(); - } - - @Override - public void setPolicyId(String policyId) { - metadata.setPolicyId(policyId); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaPermissionEntity)) return false; - return Objects.equals(getId(), ((JpaPermissionEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/entity/JpaPermissionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/entity/JpaPermissionMetadata.java deleted file mode 100644 index c5358f2df1e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/entity/JpaPermissionMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.permission.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaPermissionMetadata extends MapPermissionTicketEntityImpl implements Serializable { - - public JpaPermissionMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaPermissionMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapStorage.java deleted file mode 100644 index d0af516217d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapStorage.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.policy; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.authorization.model.Policy; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntityDelegate; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.authorization.policy.delegate.JpaPolicyDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity; - -public class JpaPolicyMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaPolicyMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaPolicyEntity.class, Policy.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaPolicyEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("resourceServerId"), - root.get("name"), - root.get("owner"), - root.get("type")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_AUTHZ_POLICY); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaPolicyModelCriteriaBuilder(); - } - - @Override - protected MapPolicyEntity mapToEntityDelegate(JpaPolicyEntity original) { - return new MapPolicyEntityDelegate(new JpaPolicyDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java deleted file mode 100644 index 90ba9145eb8..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.policy; - -import java.util.Collection; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import jakarta.persistence.criteria.CriteriaBuilder.In; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; - -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Policy.SearchableFields; -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyConfigEntity; -import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.storage.SearchableModelField; - -public class JpaPolicyModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaPolicyModelCriteriaBuilder() { - super(JpaPolicyModelCriteriaBuilder::new); - } - - private JpaPolicyModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaPolicyModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaPolicyModelCriteriaBuilder compare(SearchableModelField modelField, ModelCriteriaBuilder.Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.ID || - modelField == SearchableFields.RESOURCE_SERVER_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.get(modelField.getName()), uuid); - }); - } else if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.NAME || - modelField == SearchableFields.TYPE) { - - validateValue(value, modelField, op, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.ASSOCIATED_POLICY_ID) { - validateValue(value, modelField, op, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.join("policyIds", JoinType.LEFT), uuid); - }); - } else if (modelField == SearchableFields.RESOURCE_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.join("resourceIds", JoinType.LEFT), uuid); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case NOT_EXISTS: - if (modelField == SearchableFields.OWNER) { - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> - cb.isNull(cb.function("->", JsonbType.class, root.get("metadata"), cb.literal("fOwner"))) - ); - } else if (modelField == SearchableFields.RESOURCE_ID) { - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - return cb.isNull(root.join("resourceIds", JoinType.LEFT)); - }); - } else if (modelField == SearchableFields.CONFIG) { - - validateValue(value, modelField, op, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("config", JoinType.LEFT); - return cb.isNull(join.get("name")); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case LIKE: - if (modelField == SearchableFields.CONFIG) { - validateValue(value, modelField, op, String.class, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("config", JoinType.LEFT); - return cb.and( - cb.equal(join.get("name"), value[0]), - cb.like(join.get("value"), value[1].toString()) - ); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case ILIKE: - if (modelField == SearchableFields.NAME || - modelField == SearchableFields.TYPE) { - - validateValue(value, modelField, op, String.class); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case IN: - if (modelField == SearchableFields.ID) { - - Set uuids = getUuidsForInOperator(value, modelField); - - if (uuids.isEmpty()) return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("id")); - uuids.forEach(in::value); - return in; - }); - } else if (modelField == SearchableFields.TYPE) { - - final Collection collectionValues = getValuesForInOperator(value, modelField); - - if (collectionValues.isEmpty()) { - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> cb.or()); - } - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("type")); - for (Object type : collectionValues) { - if (! (type instanceof String)) throw new CriterionNotSupportedException(modelField, op, type + " type is not String."); - in.value(type.toString()); - } - return in; - }); - } else if (modelField == SearchableFields.OWNER) { - - final Collection collectionValues = getValuesForInOperator(value, modelField); - - if (collectionValues.isEmpty()) return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("owner")); - collectionValues.forEach(owner -> { - if (! (owner instanceof String)) throw new CriterionNotSupportedException(modelField, op, owner + " owner is not String."); - in.value(owner.toString()); - }); - return in; - }); - } else if (modelField == SearchableFields.SCOPE_ID) { - - Set scopeUuids = getUuidsForInOperator(value, modelField); - - if (scopeUuids.isEmpty()) return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.join("scopeIds", JoinType.LEFT)); - scopeUuids.forEach(in::value); - return in; - }); - } else if (modelField == SearchableFields.RESOURCE_ID) { - - Set resourceUuids = getUuidsForInOperator(value, modelField); - - if (resourceUuids.isEmpty()) return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaPolicyModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.join("resourceIds", JoinType.LEFT)); - resourceUuids.forEach(in::value); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/delegate/JpaPolicyDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/delegate/JpaPolicyDelegateProvider.java deleted file mode 100644 index 8c0788346c8..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/delegate/JpaPolicyDelegateProvider.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.policy.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity; - -public class JpaPolicyDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaPolicyDelegateProvider(JpaPolicyEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapPolicyEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapPolicyEntityFields) { - switch ((MapPolicyEntityFields) field) { - case ID: - case NAME: - case TYPE: - case OWNER: - case REALM_ID: - case RESOURCE_SERVER_ID: - return getDelegate(); - - default: - setDelegate(em.find(JpaPolicyEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid scope field: " + field); - } - } else { - setDelegate(em.find(JpaPolicyEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyConfigEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyConfigEntity.java deleted file mode 100644 index d703a7452cd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyConfigEntity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.policy.entity; - -import java.util.Objects; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import org.keycloak.models.map.storage.jpa.JpaAttributeEntity; - -@Entity -@Table(name = "kc_authz_policy_config") -public class JpaPolicyConfigEntity extends JpaAttributeEntity { - - public JpaPolicyConfigEntity() { - } - - public JpaPolicyConfigEntity(JpaPolicyEntity root, String name, String value) { - super(root, name, value); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaPolicyConfigEntity)) return false; - JpaPolicyConfigEntity that = (JpaPolicyConfigEntity) obj; - return Objects.equals(getParent(), that.getParent()) && - Objects.equals(getName(), that.getName()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyEntity.java deleted file mode 100644 index ec820e9b757..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyEntity.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.policy.entity; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity.AbstractMapPolicyEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.representations.idm.authorization.DecisionStrategy; -import org.keycloak.representations.idm.authorization.Logic; - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_authz_policy", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "resourceServerId", "name"})}) -public class JpaPolicyEntity extends AbstractMapPolicyEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaPolicyMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID resourceServerId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String owner; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String type; - - //TODO starts here - //possible future optimization: use @OneToMany to avoid reinserting entries after every change, see: https://thorben-janssen.com/hibernate-tips-elementcollection/ - @Column(name = "scope_id") - @ElementCollection - @CollectionTable(name = "kc_authz_policy_scope", joinColumns = @JoinColumn(name = "policy_id", nullable = false)) - private final Set scopeIds = new HashSet<>(); - - @Column(name = "resource_id") - @ElementCollection - @CollectionTable(name = "kc_authz_policy_resource", joinColumns = @JoinColumn(name = "policy_id", nullable = false)) - private final Set resourceIds = new HashSet<>(); - - @Column(name = "associated_policy_id") - @ElementCollection - @CollectionTable(name = "kc_authz_policy_associated_policy", joinColumns = @JoinColumn(name = "policy_id", nullable = false)) - private final Set policyIds = new HashSet<>(); - //TODO ends here - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set config = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaPolicyEntity() { - this.metadata = new JpaPolicyMetadata(); - } - - public JpaPolicyEntity(DeepCloner cloner) { - this.metadata = new JpaPolicyMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select object without metadata(json) field. - */ - public JpaPolicyEntity(UUID id, int version, Integer entityVersion, String realmId, - UUID resourceServerId, String name, String owner, String type) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.resourceServerId = resourceServerId; - this.name = name; - this.owner = owner; - this.type = type; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_AUTHZ_POLICY; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getResourceServerId() { - if (isMetadataInitialized()) return metadata.getResourceServerId(); - return resourceServerId == null ? null : resourceServerId.toString(); - } - - @Override - public void setResourceServerId(String resourceServerId) { - metadata.setResourceServerId(resourceServerId); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public String getType() { - if (isMetadataInitialized()) return metadata.getType(); - return type; - } - - @Override - public void setType(String type) { - metadata.setType(type); - } - - @Override - public String getDescription() { - return metadata.getDescription(); - } - - @Override - public void setDescription(String description) { - metadata.setDescription(description); - } - - @Override - public DecisionStrategy getDecisionStrategy() { - return metadata.getDecisionStrategy(); - } - - @Override - public void setDecisionStrategy(DecisionStrategy decisionStrategy) { - metadata.setDecisionStrategy(decisionStrategy); - } - - @Override - public Logic getLogic() { - return metadata.getLogic(); - } - - @Override - public void setLogic(Logic logic) { - metadata.setLogic(logic); - } - - @Override - public Map getConfigs() { - return Collections.unmodifiableMap(config.stream().collect(Collectors.toMap(JpaPolicyConfigEntity::getName, JpaPolicyConfigEntity::getValue))); - } - - @Override - public void setConfigs(Map config) { - this.config.clear(); - if (config == null) return; - for (Map.Entry entry : config.entrySet()) { - setConfig(entry.getKey(), entry.getValue()); - } - } - - @Override - public String getConfig(String name) { - return config.stream() - .filter(obj -> Objects.equals(obj.getName(), name)) - .findFirst() - .map(JpaPolicyConfigEntity::getValue) - .orElse(null); - } - - @Override - public void setConfig(String name, String value) { - if (name == null || value == null || value.trim().isEmpty()) return; - JpaPolicyConfigEntity configEntity = new JpaPolicyConfigEntity(this, name, value); - if (config.contains(configEntity)) config.remove(configEntity); - config.add(configEntity); - } - - @Override - public void removeConfig(String name) { - config.removeIf(obj -> Objects.equals(obj.getName(), name)); - } - - @Override - public Set getAssociatedPolicyIds() { - return policyIds.stream().map(UUID::toString).collect(Collectors.toSet()); - } - - @Override - public void addAssociatedPolicyId(String policyId) { - policyIds.add(StringKeyConverter.UUIDKey.INSTANCE.fromString(policyId)); - } - - @Override - public void removeAssociatedPolicyId(String policyId) { - policyIds.remove(StringKeyConverter.UUIDKey.INSTANCE.fromString(policyId)); - } - - @Override - public Set getResourceIds() { - return resourceIds.stream().map(UUID::toString).collect(Collectors.toSet()); - } - - @Override - public void addResourceId(String resourceId) { - resourceIds.add(StringKeyConverter.UUIDKey.INSTANCE.fromString(resourceId)); - } - - @Override - public void removeResourceId(String resourceId) { - resourceIds.remove(StringKeyConverter.UUIDKey.INSTANCE.fromString(resourceId)); - } - - @Override - public Set getScopeIds() { - return scopeIds.stream().map(UUID::toString).collect(Collectors.toSet()); - } - - @Override - public void addScopeId(String scopeId) { - scopeIds.add(StringKeyConverter.UUIDKey.INSTANCE.fromString(scopeId)); - } - - @Override - public void removeScopeId(String scopeId) { - scopeIds.remove(StringKeyConverter.UUIDKey.INSTANCE.fromString(scopeId)); - } - - @Override - public String getOwner() { - if (isMetadataInitialized()) return metadata.getOwner(); - return owner; - } - - @Override - public void setOwner(String owner) { - metadata.setOwner(owner); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaPolicyEntity)) return false; - return Objects.equals(getId(), ((JpaPolicyEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyMetadata.java deleted file mode 100644 index f22b3494301..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/entity/JpaPolicyMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.policy.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authorization.entity.MapPolicyEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaPolicyMetadata extends MapPolicyEntityImpl implements Serializable { - - public JpaPolicyMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaPolicyMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapStorage.java deleted file mode 100644 index c68b1f46d2f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapStorage.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resource; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.authorization.model.Resource; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntityDelegate; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.authorization.resource.delegate.JpaResourceDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity; - -public class JpaResourceMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaResourceMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaResourceEntity.class, Resource.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaResourceEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("resourceServerId"), - root.get("name"), - root.get("type"), - root.get("owner")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaResourceModelCriteriaBuilder(); - } - - @Override - protected MapResourceEntity mapToEntityDelegate(JpaResourceEntity original) { - return new MapResourceEntityDelegate(new JpaResourceDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java deleted file mode 100644 index a2ee9814d87..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resource; - -import java.util.Collection; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import jakarta.persistence.criteria.CriteriaBuilder.In; -import jakarta.persistence.criteria.JoinType; - -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.Resource.SearchableFields; -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaResourceModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaResourceModelCriteriaBuilder() { - super(JpaResourceModelCriteriaBuilder::new); - } - - private JpaResourceModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaResourceModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaResourceModelCriteriaBuilder compare(SearchableModelField modelField, ModelCriteriaBuilder.Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.ID || - modelField == SearchableFields.RESOURCE_SERVER_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.get(modelField.getName()), uuid); - }); - } else if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.NAME || - modelField == SearchableFields.TYPE || - modelField == SearchableFields.OWNER) { - - validateValue(value, modelField, op, String.class); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.OWNER_MANAGED_ACCESS) { - validateValue(value, modelField, op, Boolean.class); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.URI) { - validateValue(value, modelField, op, String.class); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - return cb.equal(root.join("uris", JoinType.LEFT), value[0]); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case NE: - if (modelField == SearchableFields.OWNER) { - validateValue(value, modelField, op, String.class); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]).not() - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case EXISTS: - if (modelField == SearchableFields.URI) { - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - return cb.isNotNull(root.join("uris", JoinType.LEFT)); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case ILIKE: - if (modelField == SearchableFields.NAME || - modelField == SearchableFields.TYPE) { - - validateValue(value, modelField, op, String.class); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case IN: - if (modelField == SearchableFields.ID) { - - Set uuids = getUuidsForInOperator(value, modelField); - - if (uuids.isEmpty()) return new JpaResourceModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("id")); - uuids.forEach(uuid -> in.value(uuid)); - return in; - }); - } else if (modelField == SearchableFields.OWNER) { - - final Collection collectionValues = getValuesForInOperator(value, modelField); - - if (collectionValues.isEmpty()) return new JpaResourceModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("owner")); - collectionValues.forEach(owner -> { - if (! (owner instanceof String)) throw new CriterionNotSupportedException(modelField, op, owner + " owner is not String."); - in.value(owner.toString()); - }); - return in; - }); - } else if (modelField == SearchableFields.SCOPE_ID) { - - Set scopeUuids = getUuidsForInOperator(value, modelField); - - if (scopeUuids.isEmpty()) return new JpaResourceModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.join("scopeIds", JoinType.LEFT)); - scopeUuids.forEach(in::value); - return in; - }); - } else if (modelField == SearchableFields.URI) { - - final Collection collectionValues = getValuesForInOperator(value, modelField); - - if (collectionValues.isEmpty()) return new JpaResourceModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaResourceModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.join("uris", JoinType.LEFT)); - collectionValues.forEach(uri -> { - if (! (uri instanceof String)) throw new CriterionNotSupportedException(modelField, op, uri + " uri is not String."); - in.value(uri.toString()); - }); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/delegate/JpaResourceDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/delegate/JpaResourceDelegateProvider.java deleted file mode 100644 index d91a531d473..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/delegate/JpaResourceDelegateProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resource.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; - -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity; - -public class JpaResourceDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaResourceDelegateProvider(JpaResourceEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapResourceEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapResourceEntityFields) { - switch ((MapResourceEntityFields) field) { - case ID: - case NAME: - case TYPE: - case OWNER: - case REALM_ID: - case RESOURCE_SERVER_ID: - return getDelegate(); - - case ATTRIBUTES: - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaResourceEntity.class); - Root root = query.from(JpaResourceEntity.class); - root.fetch("attributes", JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - setDelegate(em.createQuery(query).getSingleResult()); - break; - - default: - setDelegate(em.find(JpaResourceEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid resource field: " + field); - } - } else { - setDelegate(em.find(JpaResourceEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceAttributeEntity.java deleted file mode 100644 index 8d9519ae148..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceAttributeEntity.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resource.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import org.keycloak.models.map.storage.jpa.JpaAttributeEntity; - -@Entity -@Table(name = "kc_authz_resource_attribute") -public class JpaResourceAttributeEntity extends JpaAttributeEntity { - - public JpaResourceAttributeEntity() { - } - - public JpaResourceAttributeEntity(JpaResourceEntity root, String name, String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceEntity.java deleted file mode 100644 index 45620b2012b..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceEntity.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resource.entity; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authorization.entity.MapResourceEntity.AbstractMapResourceEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_authz_resource", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "resourceServerId", "name", "owner"})}) -public class JpaResourceEntity extends AbstractMapResourceEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaResourceMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID resourceServerId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String type; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String owner; - - //TODO starts here - //possible future optimization: use @OneToMany to avoid reinserting entries after every change, see: https://thorben-janssen.com/hibernate-tips-elementcollection/ - @Column(name = "uri") - @ElementCollection - @CollectionTable(name = "kc_authz_resource_uri", joinColumns = @JoinColumn(name = "resource_id", nullable = false)) - private Set uris = new HashSet<>(); - - @Column(name = "scope_id") - @ElementCollection - @CollectionTable(name = "kc_authz_resource_scope", joinColumns = @JoinColumn(name = "resource_id", nullable = false)) - private Set scopeIds = new HashSet<>(); - //TODO ends here - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaResourceEntity() { - this.metadata = new JpaResourceMetadata(); - } - - public JpaResourceEntity(DeepCloner cloner) { - this.metadata = new JpaResourceMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select object without metadata(json) field. - */ - public JpaResourceEntity(UUID id, int version, Integer entityVersion, String realmId, - UUID resourceServerId, String name, String type, String owner) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.resourceServerId = resourceServerId; - this.name = name; - this.type = type; - this.owner = owner; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public String getDisplayName() { - return metadata.getDisplayName(); - } - - @Override - public void setDisplayName(String displayName) { - metadata.setDisplayName(displayName); - } - - @Override - public Set getUris() { - return uris; - } - - @Override - public void setUris(Set uris) { - uris = uris == null ? new HashSet<>() : uris; - this.uris = uris; - } - - @Override - public String getType() { - if (isMetadataInitialized()) return metadata.getType(); - return type; - } - - @Override - public void setType(String type) { - metadata.setType(type); - } - - @Override - public String getIconUri() { - return metadata.getIconUri(); - } - - @Override - public void setIconUri(String iconUri) { - metadata.setIconUri(iconUri); - } - - @Override - public String getOwner() { - if (isMetadataInitialized()) return metadata.getOwner(); - return owner; - } - - @Override - public void setOwner(String owner) { - metadata.setOwner(owner); - } - - @Override - public Boolean isOwnerManagedAccess() { - return metadata.isOwnerManagedAccess(); - } - - @Override - public void setOwnerManagedAccess(Boolean ownerManagedAccess) { - metadata.setOwnerManagedAccess(ownerManagedAccess); - } - - @Override - public void setResourceServerId(String resourceServerId) { - metadata.setResourceServerId(resourceServerId); - } - - @Override - public String getResourceServerId() { - if (isMetadataInitialized()) return metadata.getResourceServerId(); - return resourceServerId == null ? null : resourceServerId.toString(); - } - - @Override - public Set getScopeIds() { - return scopeIds.stream().map(UUID::toString).collect(Collectors.toSet()); - } - - @Override - public void setScopeIds(Set scopeIds) { - scopeIds = scopeIds == null ? new HashSet<>() : scopeIds; - this.scopeIds = scopeIds.stream() - .map(StringKeyConverter.UUIDKey.INSTANCE::fromString) - .collect(Collectors.toSet()); - } - - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaResourceAttributeEntity attribute : attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - for (Map.Entry> attrEntry : attributes.entrySet()) { - setAttribute(attrEntry.getKey(), attrEntry.getValue()); - } - } - } - - @Override - public List getAttribute(String name) { - return attributes.stream() - .filter(a -> Objects.equals(a.getName(), name)) - .map(JpaResourceAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public void setAttribute(String name, List values) { - removeAttribute(name); - for (String value : values) { - JpaResourceAttributeEntity attribute = new JpaResourceAttributeEntity(this, name, value); - attributes.add(attribute); - } - } - - @Override - public void removeAttribute(String name) { - attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaResourceEntity)) return false; - return Objects.equals(getId(), ((JpaResourceEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceMetadata.java deleted file mode 100644 index 24071e19619..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/entity/JpaResourceMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resource.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authorization.entity.MapResourceEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaResourceMetadata extends MapResourceEntityImpl implements Serializable { - - public JpaResourceMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaResourceMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapStorage.java deleted file mode 100644 index 08f0e12a423..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resourceServer; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntityDelegate; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.delegate.JpaResourceServerDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerEntity; - -public class JpaResourceServerMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaResourceServerMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaResourceServerEntity.class, ResourceServer.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaResourceServerEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("clientId")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaResourceServerModelCriteriaBuilder(); - } - - @Override - protected MapResourceServerEntity mapToEntityDelegate(JpaResourceServerEntity original) { - return new MapResourceServerEntityDelegate(new JpaResourceServerDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java deleted file mode 100644 index 428764b0e7a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resourceServer; - -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.ResourceServer.SearchableFields; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaResourceServerModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaResourceServerModelCriteriaBuilder() { - super(JpaResourceServerModelCriteriaBuilder::new); - } - - private JpaResourceServerModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaResourceServerModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaResourceServerModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.CLIENT_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaResourceServerModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/delegate/JpaResourceServerDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/delegate/JpaResourceServerDelegateProvider.java deleted file mode 100644 index 5a5d911fc79..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/delegate/JpaResourceServerDelegateProvider.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resourceServer.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; - -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerEntity; - -public class JpaResourceServerDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaResourceServerDelegateProvider(JpaResourceServerEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapResourceServerEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapResourceServerEntityFields) { - switch ((MapResourceServerEntityFields) field) { - case ID: - case REALM_ID: - case CLIENT_ID: - return getDelegate(); - - default: - setDelegate(em.find(JpaResourceServerEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid resource server field: " + field); - } - } else { - setDelegate(em.find(JpaResourceServerEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/entity/JpaResourceServerEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/entity/JpaResourceServerEntity.java deleted file mode 100644 index fef22978d2a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/entity/JpaResourceServerEntity.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resourceServer.entity; - -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity.AbstractMapResourceServerEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.representations.idm.authorization.DecisionStrategy; -import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; - - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_authz_resource_server", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId"})}) -public class JpaResourceServerEntity extends AbstractMapResourceServerEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaResourceServerMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String clientId; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaResourceServerEntity() { - this.metadata = new JpaResourceServerMetadata(); - } - - public JpaResourceServerEntity(DeepCloner cloner) { - this.metadata = new JpaResourceServerMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select object without metadata(json) field. - */ - public JpaResourceServerEntity(UUID id, int version, Integer entityVersion, String realmId, String clientId) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.clientId = clientId; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getClientId() { - if (isMetadataInitialized()) return metadata.getClientId(); - return clientId; - } - - @Override - public void setClientId(String clientId) { - metadata.setClientId(clientId); - } - - @Override - public Boolean isAllowRemoteResourceManagement() { - return metadata.isAllowRemoteResourceManagement(); - } - - @Override - public void setAllowRemoteResourceManagement(Boolean allowRemoteResourceManagement) { - metadata.setAllowRemoteResourceManagement(allowRemoteResourceManagement); - } - - @Override - public PolicyEnforcementMode getPolicyEnforcementMode() { - return metadata.getPolicyEnforcementMode(); - } - - @Override - public void setPolicyEnforcementMode(PolicyEnforcementMode policyEnforcementMode) { - metadata.setPolicyEnforcementMode(policyEnforcementMode); - } - - @Override - public DecisionStrategy getDecisionStrategy() { - return metadata.getDecisionStrategy(); - } - - @Override - public void setDecisionStrategy(DecisionStrategy decisionStrategy) { - metadata.setDecisionStrategy(decisionStrategy); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaResourceServerEntity)) return false; - return Objects.equals(getId(), ((JpaResourceServerEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/entity/JpaResourceServerMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/entity/JpaResourceServerMetadata.java deleted file mode 100644 index 3b969c0cf32..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/entity/JpaResourceServerMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.resourceServer.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaResourceServerMetadata extends MapResourceServerEntityImpl implements Serializable { - - public JpaResourceServerMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaResourceServerMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapStorage.java deleted file mode 100644 index 06f4377f94d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapStorage.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.scope; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.authorization.model.Scope; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntityDelegate; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.authorization.scope.delagate.JpaScopeDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEntity; - -public class JpaScopeMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaScopeMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaScopeEntity.class, Scope.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaScopeEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("resourceServerId"), - root.get("name")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaScopeModelCriteriaBuilder(); - } - - @Override - protected MapScopeEntity mapToEntityDelegate(JpaScopeEntity original) { - return new MapScopeEntityDelegate(new JpaScopeDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java deleted file mode 100644 index c271e0050df..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.scope; - -import java.util.Collection; -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.criteria.CriteriaBuilder.In; - -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.model.Scope.SearchableFields; -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaScopeModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaScopeModelCriteriaBuilder() { - super(JpaScopeModelCriteriaBuilder::new); - } - - private JpaScopeModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaScopeModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaScopeModelCriteriaBuilder compare(SearchableModelField modelField, ModelCriteriaBuilder.Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.ID || - modelField == SearchableFields.RESOURCE_SERVER_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaScopeModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.get(modelField.getName()), uuid); - }); - } else if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.NAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaScopeModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case ILIKE: - if (modelField == SearchableFields.NAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaScopeModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case IN: - if (modelField == SearchableFields.ID) { - - final Collection collectionValues = getValuesForInOperator(value, modelField); - - if (collectionValues.isEmpty()) { - return new JpaScopeModelCriteriaBuilder((cb, query, root) -> cb.or()); - } - - return new JpaScopeModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("id")); - for (Object id : collectionValues) { - try { - in.value(UUIDKey.INSTANCE.fromString(Objects.toString(id, null))); - } catch (IllegalArgumentException e) { - throw new CriterionNotSupportedException(modelField, op, id + " id is not in uuid format.", e); - } - } - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/delagate/JpaScopeDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/delagate/JpaScopeDelegateProvider.java deleted file mode 100644 index d7fba94df99..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/delagate/JpaScopeDelegateProvider.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.scope.delagate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; - -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEntity; - -public class JpaScopeDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaScopeDelegateProvider(JpaScopeEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapScopeEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapScopeEntityFields) { - switch ((MapScopeEntityFields) field) { - case ID: - case NAME: - case REALM_ID: - case RESOURCE_SERVER_ID: - return getDelegate(); - - default: - setDelegate(em.find(JpaScopeEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid scope field: " + field); - } - } else { - setDelegate(em.find(JpaScopeEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/entity/JpaScopeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/entity/JpaScopeEntity.java deleted file mode 100644 index 3d2f935489e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/entity/JpaScopeEntity.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.scope.entity; - -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.authorization.entity.MapScopeEntity.AbstractMapScopeEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_authz_scope", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "resourceServerId", "name"})}) -public class JpaScopeEntity extends AbstractMapScopeEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaScopeMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private UUID resourceServerId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaScopeEntity() { - this.metadata = new JpaScopeMetadata(); - } - - public JpaScopeEntity(DeepCloner cloner) { - this.metadata = new JpaScopeMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select object without metadata(json) field. - */ - public JpaScopeEntity(UUID id, int version, Integer entityVersion, String realmId, - UUID resourceServerId, String name) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.resourceServerId = resourceServerId; - this.name = name; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getResourceServerId() { - if (isMetadataInitialized()) return metadata.getResourceServerId(); - return resourceServerId == null ? null : resourceServerId.toString(); - } - - @Override - public void setResourceServerId(String resourceServerId) { - metadata.setResourceServerId(resourceServerId); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public String getDisplayName() { - return metadata.getDisplayName(); - } - - @Override - public void setDisplayName(String displayName) { - metadata.setDisplayName(displayName); - } - - @Override - public String getIconUri() { - return metadata.getIconUri(); - } - - @Override - public void setIconUri(String iconUri) { - metadata.setIconUri(iconUri); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaScopeEntity)) return false; - return Objects.equals(getId(), ((JpaScopeEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/entity/JpaScopeMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/entity/JpaScopeMetadata.java deleted file mode 100644 index fbfdd976d57..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/entity/JpaScopeMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.authorization.scope.entity; - -import java.io.Serializable; -import org.keycloak.models.map.authorization.entity.MapScopeEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaScopeMetadata extends MapScopeEntityImpl implements Serializable { - - public JpaScopeMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaScopeMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapStorage.java deleted file mode 100644 index 3ffe39b516a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapStorage.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.storage.jpa.client; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityDelegate; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.client.delegate.JpaClientDelegateProvider; - -public class JpaClientMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaClientMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaClientEntity.class, ClientModel.class, em); - } - - @Override - public Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaClientEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("clientId"), - root.get("protocol"), - root.get("enabled") - ); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_CLIENT); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaClientModelCriteriaBuilder(); - } - - @Override - protected MapClientEntity mapToEntityDelegate(JpaClientEntity original) { - return new MapClientEntityDelegate(new JpaClientDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java deleted file mode 100644 index 5e2a6af50c3..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.storage.jpa.client; - -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientModel.SearchableFields; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaClientModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaClientModelCriteriaBuilder() { - super(JpaClientModelCriteriaBuilder::new); - } - - private JpaClientModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaClientModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaClientModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.CLIENT_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaClientModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.ENABLED) { - validateValue(value, modelField, op, Boolean.class); - - return new JpaClientModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.SCOPE_MAPPING_ROLE) { - validateValue(value, modelField, op, String.class); - - return new JpaClientModelCriteriaBuilder((cb, query, root) -> - cb.isTrue(cb.function("@>", - Boolean.TYPE, - cb.function("->", JsonbType.class, root.get("metadata"), cb.literal("fScopeMappings")), - cb.literal(convertToJson(value[0])))) - ); - } else if (modelField == SearchableFields.ALWAYS_DISPLAY_IN_CONSOLE) { - validateValue(value, modelField, op, Boolean.class); - - return new JpaClientModelCriteriaBuilder((cb, query, root) -> - cb.equal( - cb.function("->>", String.class, root.get("metadata"), cb.literal("fAlwaysDisplayInConsole")), - convertToJson(value[0])) - ); - } else if (modelField == SearchableFields.ATTRIBUTE) { - validateValue(value, modelField, op, String.class, String.class); - - return new JpaClientModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("attributes", JoinType.LEFT); - return cb.and( - cb.equal(join.get("name"), value[0]), - hashExpression(cb, join, "value_hash", value[1]), - cb.equal(join.get("value"), value[1]) - ); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case ILIKE: - if (modelField == SearchableFields.CLIENT_ID) { - validateValue(value, modelField, op, String.class); - - return new JpaClientModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()) - ); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/delegate/JpaClientDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/delegate/JpaClientDelegateProvider.java deleted file mode 100644 index a5e0dd94d8d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/delegate/JpaClientDelegateProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.storage.jpa.client.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; - -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity; - -public class JpaClientDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaClientDelegateProvider(JpaClientEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapClientEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapClientEntityFields) { - switch ((MapClientEntityFields) field) { - case ID: - case REALM_ID: - case CLIENT_ID: - case PROTOCOL: - case ENABLED: - return getDelegate(); - - case ATTRIBUTES: - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaClientEntity.class); - Root root = query.from(JpaClientEntity.class); - root.fetch("attributes", JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - - setDelegate(em.createQuery(query).getSingleResult()); - break; - - default: - setDelegate(em.find(JpaClientEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid client field: " + field); - } - } else { - setDelegate(em.find(JpaClientEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientAttributeEntity.java deleted file mode 100644 index 3c09a3cebbb..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientAttributeEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.storage.jpa.client.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -@Entity -@Table(name = "kc_client_attribute", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaClientAttributeEntity extends JpaAttributeEntityWithHashValue { - - public JpaClientAttributeEntity() { - } - - public JpaClientAttributeEntity(JpaClientEntity root, String name, String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientEntity.java deleted file mode 100644 index 58b111ca9fe..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientEntity.java +++ /dev/null @@ -1,591 +0,0 @@ -/* - * 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.storage.jpa.client.entity; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.common.DeepCloner; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT; - -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import java.util.Optional; - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_client", - uniqueConstraints = { - @UniqueConstraint( - columnNames = {"realmId", "clientId"} - ) -}) -public class JpaClientEntity extends AbstractClientEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaClientMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String clientId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String protocol; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Boolean enabled; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaClientEntity() { - this.metadata = new JpaClientMetadata(); - } - - public JpaClientEntity(DeepCloner cloner) { - this.metadata = new JpaClientMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select client without metadata(json) field. - */ - public JpaClientEntity(UUID id, int version, Integer entityVersion, String realmId, String clientId, - String protocol, Boolean enabled) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.clientId = clientId; - this.protocol = protocol; - this.enabled = enabled; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_CLIENT; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getClientId() { - if (isMetadataInitialized()) return metadata.getClientId(); - return clientId; - } - - @Override - public void setClientId(String clientId) { - metadata.setClientId(clientId); - } - - @Override - public void setEnabled(Boolean enabled) { - metadata.setEnabled(enabled); - } - - @Override - public Boolean isEnabled() { - if (isMetadataInitialized()) return metadata.isEnabled(); - return enabled; - } - - @Override - public Map getClientScopes() { - return metadata.getClientScopes(); - } - - @Override - public void setClientScope(String id, Boolean defaultScope) { - metadata.setClientScope(id, defaultScope); - } - - @Override - public void removeClientScope(String id) { - metadata.removeClientScope(id); - } - - @Override - public Set getProtocolMappers() { - return metadata.getProtocolMappers(); - } - - @Override - public Optional getProtocolMapper(String id) { - return metadata.getProtocolMapper(id); - } - - @Override - public void addProtocolMapper(MapProtocolMapperEntity mapping) { - metadata.addProtocolMapper(mapping); - } - - @Override - public void removeProtocolMapper(String id) { - metadata.removeProtocolMapper(id); - } - - @Override - public void addRedirectUri(String redirectUri) { - metadata.addRedirectUri(redirectUri); - } - - @Override - public Set getRedirectUris() { - return metadata.getRedirectUris(); - } - - @Override - public void removeRedirectUri(String redirectUri) { - metadata.removeRedirectUri(redirectUri); - } - - @Override - public void setRedirectUris(Set redirectUris) { - metadata.setRedirectUris(redirectUris); - } - - @Override - public void addScopeMapping(String id) { - metadata.addScopeMapping(id); - } - - @Override - public void removeScopeMapping(String id) { - metadata.removeScopeMapping(id); - } - - @Override - public Collection getScopeMappings() { - return metadata.getScopeMappings(); - } - - @Override - public void addWebOrigin(String webOrigin) { - metadata.addWebOrigin(webOrigin); - } - - @Override - public Set getWebOrigins() { - return metadata.getWebOrigins(); - } - - @Override - public void removeWebOrigin(String webOrigin) { - metadata.removeWebOrigin(webOrigin); - } - - @Override - public void setWebOrigins(Set webOrigins) { - metadata.setWebOrigins(webOrigins); - } - - @Override - public String getAuthenticationFlowBindingOverride(String binding) { - return metadata.getAuthenticationFlowBindingOverride(binding); - } - - @Override - public Map getAuthenticationFlowBindingOverrides() { - return metadata.getAuthenticationFlowBindingOverrides(); - } - - @Override - public void removeAuthenticationFlowBindingOverride(String binding) { - metadata.removeAuthenticationFlowBindingOverride(binding); - } - - @Override - public void setAuthenticationFlowBindingOverride(String binding, String flowId) { - metadata.setAuthenticationFlowBindingOverride(binding, flowId); - } - - @Override - public String getBaseUrl() { - return metadata.getBaseUrl(); - } - - @Override - public void setBaseUrl(String baseUrl) { - metadata.setBaseUrl(baseUrl); - } - - @Override - public String getClientAuthenticatorType() { - return metadata.getClientAuthenticatorType(); - } - - @Override - public void setClientAuthenticatorType(String clientAuthenticatorType) { - metadata.setClientAuthenticatorType(clientAuthenticatorType); - } - - @Override - public String getDescription() { - return metadata.getDescription(); - } - - @Override - public void setDescription(String description) { - metadata.setDescription(description); - } - - @Override - public String getManagementUrl() { - return metadata.getManagementUrl(); - } - - @Override - public void setManagementUrl(String managementUrl) { - metadata.setManagementUrl(managementUrl); - } - - @Override - public String getName() { - return metadata.getName(); - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public Integer getNodeReRegistrationTimeout() { - return metadata.getNodeReRegistrationTimeout(); - } - - @Override - public void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout) { - metadata.setNodeReRegistrationTimeout(nodeReRegistrationTimeout); - } - - @Override - public Long getNotBefore() { - return metadata.getNotBefore(); - } - - @Override - public void setNotBefore(Long notBefore) { - metadata.setNotBefore(notBefore); - } - - @Override - public String getProtocol() { - if (isMetadataInitialized()) return metadata.getProtocol(); - return protocol; - } - - @Override - public void setProtocol(String protocol) { - metadata.setProtocol(protocol); - } - - @Override - public String getRegistrationToken() { - return metadata.getRegistrationToken(); - } - - @Override - public void setRegistrationToken(String registrationToken) { - metadata.setRegistrationToken(registrationToken); - } - - @Override - public String getRootUrl() { - return metadata.getRootUrl(); - } - - @Override - public void setRootUrl(String rootUrl) { - metadata.setRootUrl(rootUrl); - } - - @Override - public Set getScope() { - return metadata.getScope(); - } - - @Override - public void setScope(Set scope) { - metadata.setScope(scope); - } - - @Override - public String getSecret() { - return metadata.getSecret(); - } - - @Override - public void setSecret(String secret) { - metadata.setSecret(secret); - } - - @Override - public Boolean isAlwaysDisplayInConsole() { - return metadata.isAlwaysDisplayInConsole(); - } - - @Override - public void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole) { - metadata.setAlwaysDisplayInConsole(alwaysDisplayInConsole); - } - - @Override - public Boolean isBearerOnly() { - return metadata.isBearerOnly(); - } - - @Override - public void setBearerOnly(Boolean bearerOnly) { - metadata.setBearerOnly(bearerOnly); - } - - @Override - public Boolean isConsentRequired() { - return metadata.isConsentRequired(); - } - - @Override - public void setConsentRequired(Boolean consentRequired) { - metadata.setConsentRequired(consentRequired); - } - - @Override - public Boolean isDirectAccessGrantsEnabled() { - return metadata.isDirectAccessGrantsEnabled(); - } - - @Override - public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) { - metadata.setDirectAccessGrantsEnabled(directAccessGrantsEnabled); - } - - @Override - public Boolean isFrontchannelLogout() { - return metadata.isFrontchannelLogout(); - } - - @Override - public void setFrontchannelLogout(Boolean frontchannelLogout) { - metadata.setFrontchannelLogout(frontchannelLogout); - } - - @Override - public Boolean isFullScopeAllowed() { - return metadata.isFullScopeAllowed(); - } - - @Override - public void setFullScopeAllowed(Boolean fullScopeAllowed) { - metadata.setFullScopeAllowed(fullScopeAllowed); - } - - @Override - public Boolean isImplicitFlowEnabled() { - return metadata.isImplicitFlowEnabled(); - } - - @Override - public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) { - metadata.setImplicitFlowEnabled(implicitFlowEnabled); - } - - @Override - public Boolean isPublicClient() { - return metadata.isPublicClient(); - } - - @Override - public void setPublicClient(Boolean publicClient) { - metadata.setPublicClient(publicClient); - } - - @Override - public Boolean isServiceAccountsEnabled() { - return metadata.isServiceAccountsEnabled(); - } - - @Override - public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) { - metadata.setServiceAccountsEnabled(serviceAccountsEnabled); - } - - @Override - public Boolean isStandardFlowEnabled() { - return metadata.isStandardFlowEnabled(); - } - - @Override - public void setStandardFlowEnabled(Boolean standardFlowEnabled) { - metadata.setStandardFlowEnabled(standardFlowEnabled); - } - - @Override - public Boolean isSurrogateAuthRequired() { - return metadata.isSurrogateAuthRequired(); - } - - @Override - public void setSurrogateAuthRequired(Boolean surrogateAuthRequired) { - metadata.setSurrogateAuthRequired(surrogateAuthRequired); - } - - @Override - public void removeAttribute(String name) { - attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - @Override - public void setAttribute(String name, List values) { - removeAttribute(name); - for (String value : values) { - JpaClientAttributeEntity attribute = new JpaClientAttributeEntity(this, name, value); - attributes.add(attribute); - } - } - - @Override - public List getAttribute(String name) { - return attributes.stream() - .filter(a -> Objects.equals(a.getName(), name)) - .map(JpaClientAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaClientAttributeEntity attribute : attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - for (Map.Entry> attrEntry : attributes.entrySet()) { - setAttribute(attrEntry.getKey(), attrEntry.getValue()); - } - } - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaClientEntity)) return false; - return Objects.equals(getId(), ((JpaClientEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientMetadata.java deleted file mode 100644 index 07ac31dbda6..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/entity/JpaClientMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.storage.jpa.client.entity; - -import java.io.Serializable; -import org.keycloak.models.map.client.MapClientEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaClientMetadata extends MapClientEntityImpl implements Serializable { - - public JpaClientMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaClientMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/JpaClientScopeMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/JpaClientScopeMapStorage.java deleted file mode 100644 index 63d1295a586..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/JpaClientScopeMapStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.clientScope; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntityDelegate; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.clientScope.delegate.JpaClientScopeDelegateProvider; -import org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeEntity; - -public class JpaClientScopeMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaClientScopeMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaClientScopeEntity.class, ClientScopeModel.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaClientScopeEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("name")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_CLIENT_SCOPE); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaClientScopeModelCriteriaBuilder(); - } - - @Override - protected MapClientScopeEntity mapToEntityDelegate(JpaClientScopeEntity original) { - return new MapClientScopeEntityDelegate(new JpaClientScopeDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/JpaClientScopeModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/JpaClientScopeModelCriteriaBuilder.java deleted file mode 100644 index 390e3729678..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/JpaClientScopeModelCriteriaBuilder.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.clientScope; - -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.ClientScopeModel.SearchableFields; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaClientScopeModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaClientScopeModelCriteriaBuilder() { - super(JpaClientScopeModelCriteriaBuilder::new); - } - - private JpaClientScopeModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaClientScopeModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaClientScopeModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.NAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaClientScopeModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/delegate/JpaClientScopeDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/delegate/JpaClientScopeDelegateProvider.java deleted file mode 100644 index 01fc6df498b..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/delegate/JpaClientScopeDelegateProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.clientScope.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; - -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntityFields; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeEntity; - -public class JpaClientScopeDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaClientScopeDelegateProvider(JpaClientScopeEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapClientScopeEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapClientScopeEntityFields) { - switch ((MapClientScopeEntityFields) field) { - case ID: - case REALM_ID: - case NAME: - return getDelegate(); - - case ATTRIBUTES: - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaClientScopeEntity.class); - Root root = query.from(JpaClientScopeEntity.class); - root.fetch("attributes", JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - - setDelegate(em.createQuery(query).getSingleResult()); - break; - - default: - setDelegate(em.find(JpaClientScopeEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid client scope field: " + field); - } - } else { - setDelegate(em.find(JpaClientScopeEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeAttributeEntity.java deleted file mode 100644 index 06aff2f9800..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeAttributeEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.clientScope.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -@Entity -@Table(name = "kc_client_scope_attribute", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaClientScopeAttributeEntity extends JpaAttributeEntityWithHashValue { - - public JpaClientScopeAttributeEntity() { - } - - public JpaClientScopeAttributeEntity(JpaClientScopeEntity root, String name, String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeEntity.java deleted file mode 100644 index 1a1b6adbb2e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeEntity.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.clientScope.entity; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntity.AbstractClientScopeEntity; -import org.keycloak.models.map.common.DeepCloner; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE; - -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import java.util.Optional; - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_client_scope", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "name"})}) -public class JpaClientScopeEntity extends AbstractClientScopeEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaClientScopeMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaClientScopeEntity() { - this.metadata = new JpaClientScopeMetadata(); - } - - public JpaClientScopeEntity(DeepCloner cloner) { - this.metadata = new JpaClientScopeMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select client without metadata(json) field. - */ - public JpaClientScopeEntity(UUID id, int version, Integer entityVersion, String realmId, String name) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.name = name; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_CLIENT_SCOPE; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public Set getProtocolMappers() { - return metadata.getProtocolMappers(); - } - - @Override - public Optional getProtocolMapper(String id) { - return metadata.getProtocolMapper(id); - } - - @Override - public void addProtocolMapper(MapProtocolMapperEntity mapping) { - metadata.addProtocolMapper(mapping); - } - - @Override - public void removeProtocolMapper(String id) { - metadata.removeProtocolMapper(id); - } - - @Override - public void addScopeMapping(String id) { - metadata.addScopeMapping(id); - } - - @Override - public void removeScopeMapping(String id) { - metadata.removeScopeMapping(id); - } - - @Override - public Collection getScopeMappings() { - return metadata.getScopeMappings(); - } - - @Override - public String getDescription() { - return metadata.getDescription(); - } - - @Override - public void setDescription(String description) { - metadata.setDescription(description); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public String getProtocol() { - return metadata.getProtocol(); - } - - @Override - public void setProtocol(String protocol) { - metadata.setProtocol(protocol); - } - - @Override - public void removeAttribute(String name) { - attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - @Override - public void setAttribute(String name, List values) { - removeAttribute(name); - for (String value : values) { - JpaClientScopeAttributeEntity attribute = new JpaClientScopeAttributeEntity(this, name, value); - attributes.add(attribute); - } - } - - @Override - public List getAttribute(String name) { - return attributes.stream() - .filter(a -> Objects.equals(a.getName(), name)) - .map(JpaClientScopeAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaClientScopeAttributeEntity attribute : attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - for (Map.Entry> attrEntry : attributes.entrySet()) { - setAttribute(attrEntry.getKey(), attrEntry.getValue()); - } - } - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaClientScopeEntity)) return false; - return Objects.equals(getId(), ((JpaClientScopeEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeMetadata.java deleted file mode 100644 index 2a07a55f49c..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientScope/entity/JpaClientScopeMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.clientScope.entity; - -import java.io.Serializable; -import org.keycloak.models.map.clientscope.MapClientScopeEntityImpl; -import org.keycloak.models.map.common.DeepCloner; - -public class JpaClientScopeMetadata extends MapClientScopeEntityImpl implements Serializable { - - public JpaClientScopeMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaClientScopeMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapStorage.java deleted file mode 100644 index e097f7e5428..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.admin; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.event.admin.entity.JpaAdminEventEntity; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT; - -/** - * A {@link MapStorage} implementation for admin event entities. - * - * @author Stefan Guilhen - */ -public class JpaAdminEventMapStorage extends JpaMapStorage { - - public JpaAdminEventMapStorage(KeycloakSession session, final EntityManager em) { - super(session, JpaAdminEventEntity.class, AdminEvent.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return root; - } - - @Override - protected void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_ADMIN_EVENT); - } - - @Override - protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaAdminEventModelCriteriaBuilder(); - } - - @Override - protected MapAdminEventEntity mapToEntityDelegate(JpaAdminEventEntity original) { - return original; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java deleted file mode 100644 index dfc37184d95..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.admin; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import jakarta.persistence.criteria.CriteriaBuilder; - -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.event.admin.entity.JpaAdminEventEntity; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.util.EnumWithStableIndex; - -/** - * A {@link JpaModelCriteriaBuilder} implementation for admin events. - * - * @author Stefan Guilhen - */ -public class JpaAdminEventModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - private static final Map FIELD_TO_JSON_PROP = new HashMap<>(); - static { - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.AUTH_CLIENT_ID.getName(), "fAuthClientId"); - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.AUTH_REALM_ID.getName(), "fAuthRealmId"); - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.AUTH_USER_ID.getName(), "fAuthUserId"); - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.AUTH_IP_ADDRESS.getName(), "fAuthIpAddress"); - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.RESOURCE_PATH.getName(), "fResourcePath"); - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.RESOURCE_TYPE.getName(), "fResourceType"); - FIELD_TO_JSON_PROP.put(AdminEvent.SearchableFields.OPERATION_TYPE.getName(), "fOperationType"); - } - - public JpaAdminEventModelCriteriaBuilder() { - super(JpaAdminEventModelCriteriaBuilder::new); - } - - private JpaAdminEventModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaAdminEventModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaAdminEventModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch(op) { - case EQ: - if (modelField == AdminEvent.SearchableFields.REALM_ID) { - - validateValue(value, modelField, op, String.class); - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == AdminEvent.SearchableFields.AUTH_CLIENT_ID || - modelField == AdminEvent.SearchableFields.AUTH_REALM_ID || - modelField == AdminEvent.SearchableFields.AUTH_USER_ID || - modelField == AdminEvent.SearchableFields.AUTH_IP_ADDRESS) { - - validateValue(value, modelField, op, String.class); - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> - cb.equal( - cb.function("->>", String.class, root.get("metadata"), - cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case LE: - if (modelField == AdminEvent.SearchableFields.TIMESTAMP) { - - validateValue(value, modelField, op, Number.class); - - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> - cb.le(root.get(modelField.getName()), (Number) value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case LT: - if (modelField == AdminEvent.SearchableFields.TIMESTAMP) { - validateValue(value, modelField, op, Number.class); - - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> - cb.lt(root.get(modelField.getName()), (Number) value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case LIKE: - if (modelField == AdminEvent.SearchableFields.RESOURCE_PATH) { - validateValue(value, modelField, op, String.class); - - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> - cb.like( - cb.function("->>", String.class, root.get("metadata"), cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))), - value[0].toString()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case GE: - if (modelField == AdminEvent.SearchableFields.TIMESTAMP) { - validateValue(value, modelField, op, Number.class); - - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> - cb.ge(root.get(modelField.getName()), (Number) value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case IN: - if (modelField == AdminEvent.SearchableFields.OPERATION_TYPE) { - Set values = super.getValuesForInOperator(value, modelField).stream() - .map(o -> ((EnumWithStableIndex) o).getStableIndex()).collect(Collectors.toSet()); - - if (values.isEmpty()) return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> { - CriteriaBuilder.In in = cb.in(cb.function("->>", String.class, root.get("metadata"), - cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))).as(Integer.class)); - values.forEach(in::value); - return in; - }); - } - else if (modelField == AdminEvent.SearchableFields.RESOURCE_TYPE) { - Set values = super.getValuesForInOperator(value, modelField).stream() - .map(Object::toString).collect(Collectors.toSet()); - - if (values.isEmpty()) return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaAdminEventModelCriteriaBuilder((cb, query, root) -> { - CriteriaBuilder.In in = cb.in(cb.function("->>", String.class, root.get("metadata"), - cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName())))); - values.forEach(in::value); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/entity/JpaAdminEventEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/entity/JpaAdminEventEntity.java deleted file mode 100644 index 4e7237fe713..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/entity/JpaAdminEventEntity.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.admin.entity; - -import java.util.Objects; -import java.util.UUID; - -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Type; -import org.keycloak.events.admin.OperationType; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT; - -/** - * JPA {@link MapAdminEventEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_admin_event") -public class JpaAdminEventEntity extends MapAdminEventEntity.AbstractAdminEventEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaAdminEventMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long timestamp; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long expiration; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaAdminEventEntity() { - this.metadata = new JpaAdminEventMetadata(); - } - - public JpaAdminEventEntity(final DeepCloner cloner) { - this.metadata = new JpaAdminEventMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (this.isMetadataInitialized()) return metadata.getEntityVersion(); - return this.entityVersion; - } - - @Override - public void setEntityVersion(final Integer entityVersion) { - this.metadata.setEntityVersion(entityVersion); - } - - @Override - public int getVersion() { - return this.version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(final String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_ADMIN_EVENT; - } - - @Override - public Long getExpiration() { - if (this.isMetadataInitialized()) return this.metadata.getExpiration(); - return this.expiration; - } - - @Override - public void setExpiration(final Long expiration) { - this.metadata.setExpiration(expiration); - } - - @Override - public Long getTimestamp() { - if (this.isMetadataInitialized()) return this.metadata.getTimestamp(); - return this.timestamp; - } - - @Override - public void setTimestamp(final Long time) { - this.metadata.setTimestamp(time); - } - - @Override - public String getRealmId() { - if (this.isMetadataInitialized()) return this.metadata.getRealmId(); - return this.realmId; - } - - @Override - public void setRealmId(final String realmId) { - this.metadata.setRealmId(realmId); - } - - @Override - public OperationType getOperationType() { - return this.metadata.getOperationType(); - } - - @Override - public void setOperationType(final OperationType operationType) { - this.metadata.setOperationType(operationType); - } - - @Override - public String getResourcePath() { - return this.metadata.getResourcePath(); - } - - @Override - public void setResourcePath(final String resourcePath) { - this.metadata.setResourcePath(resourcePath); - } - - @Override - public String getResourceType() { - return this.metadata.getResourceType(); - } - - @Override - public void setResourceType(final String resourceType) { - this.metadata.setResourceType(resourceType); - } - - @Override - public String getRepresentation() { - return this.metadata.getRepresentation(); - } - - @Override - public void setRepresentation(final String representation) { - this.metadata.setRepresentation(representation); - } - - @Override - public String getError() { - return this.metadata.getError(); - } - - @Override - public void setError(final String error) { - this.metadata.setError(error); - } - - @Override - public String getAuthClientId() { - return this.metadata.getAuthClientId(); - } - - @Override - public void setAuthClientId(final String clientId) { - this.metadata.setAuthClientId(clientId); - } - - @Override - public String getAuthRealmId() { - return this.metadata.getAuthRealmId(); - } - - @Override - public void setAuthRealmId(final String realmId) { - this.metadata.setAuthRealmId(realmId); - } - - @Override - public String getAuthUserId() { - return this.metadata.getAuthUserId(); - } - - @Override - public void setAuthUserId(final String userId) { - this.metadata.setAuthUserId(userId); - } - - @Override - public String getAuthIpAddress() { - return this.metadata.getAuthIpAddress(); - } - - @Override - public void setAuthIpAddress(final String ipAddress) { - this.metadata.setAuthIpAddress(ipAddress); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaAdminEventEntity)) return false; - return Objects.equals(getId(), ((JpaAdminEventEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/entity/JpaAdminEventMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/entity/JpaAdminEventMetadata.java deleted file mode 100644 index 8c72a792963..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/entity/JpaAdminEventMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.admin.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.events.MapAdminEventEntityImpl; - -/** - * Class that contains all the admin event metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaAdminEventMetadata extends MapAdminEventEntityImpl implements Serializable { - - public JpaAdminEventMetadata(final DeepCloner cloner) { - super(cloner); - } - - public JpaAdminEventMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapStorage.java deleted file mode 100644 index a38ad712ece..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.auth; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.events.Event; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventEntity; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_EVENT; - -/** - * A {@link MapStorage} implementation for auth event entities. - * - * @author Stefan Guilhen - */ -public class JpaAuthEventMapStorage extends JpaMapStorage { - - public JpaAuthEventMapStorage(KeycloakSession session, final EntityManager em) { - super(session, JpaAuthEventEntity.class, Event.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return root; - } - - @Override - protected void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_AUTH_EVENT); - } - - @Override - protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaAuthEventModelCriteriaBuilder(); - } - - @Override - protected MapAuthEventEntity mapToEntityDelegate(JpaAuthEventEntity original) { - return original; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java deleted file mode 100644 index 31d620229b0..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.auth; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import jakarta.persistence.criteria.CriteriaBuilder; - -import org.keycloak.events.Event; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventEntity; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.util.EnumWithStableIndex; - -public class JpaAuthEventModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - private static final Map FIELD_TO_JSON_PROP = new HashMap<>(); - static { - FIELD_TO_JSON_PROP.put(Event.SearchableFields.CLIENT_ID.getName(), "fClientId"); - FIELD_TO_JSON_PROP.put(Event.SearchableFields.USER_ID.getName(), "fUserId"); - FIELD_TO_JSON_PROP.put(Event.SearchableFields.IP_ADDRESS.getName(), "fIpAddress"); - FIELD_TO_JSON_PROP.put(Event.SearchableFields.EVENT_TYPE.getName(), "fType"); - } - - public JpaAuthEventModelCriteriaBuilder() { - super(JpaAuthEventModelCriteriaBuilder::new); - } - - private JpaAuthEventModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaAuthEventModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaAuthEventModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == Event.SearchableFields.REALM_ID) { - - validateValue(value, modelField, op, String.class); - return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == Event.SearchableFields.CLIENT_ID || - modelField == Event.SearchableFields.USER_ID || - modelField == Event.SearchableFields.IP_ADDRESS) { - - validateValue(value, modelField, op, String.class); - return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> - cb.equal( - cb.function("->>", String.class, root.get("metadata"), - cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case LE: - if (modelField == Event.SearchableFields.TIMESTAMP) { - - validateValue(value, modelField, op, Number.class); - - return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> - cb.le(root.get(modelField.getName()), (Number) value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case LT: - if (modelField == Event.SearchableFields.TIMESTAMP) { - validateValue(value, modelField, op, Number.class); - - return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> - cb.lt(root.get(modelField.getName()), (Number) value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case GE: - if (modelField == Event.SearchableFields.TIMESTAMP) { - validateValue(value, modelField, op, Number.class); - - return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> - cb.ge(root.get(modelField.getName()), (Number) value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case IN: - if (modelField == Event.SearchableFields.EVENT_TYPE) { - Set values = super.getValuesForInOperator(value, modelField).stream() - .map(o -> ((EnumWithStableIndex) o).getStableIndex()).collect(Collectors.toSet()); - - if (values.isEmpty()) return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaAuthEventModelCriteriaBuilder((cb, query, root) -> { - CriteriaBuilder.In in = cb.in(cb.function("->>", String.class, root.get("metadata"), - cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))).as(Integer.class)); - values.forEach(in::value); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventDetailEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventDetailEntity.java deleted file mode 100644 index 093f8062fc1..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventDetailEntity.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.auth.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -/** - * JPA implementation for auth event details. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_auth_event_detail", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaAuthEventDetailEntity extends JpaAttributeEntityWithHashValue { - - public JpaAuthEventDetailEntity() { - } - - public JpaAuthEventDetailEntity(final JpaAuthEventEntity root, final String name, final String value) { - super(root, name, value); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventEntity.java deleted file mode 100644 index c7e43eb32f6..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventEntity.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.auth.entity; - -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Type; -import org.keycloak.events.EventType; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_EVENT; - -/** - * JPA {@link MapAuthEventEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_auth_event") -public class JpaAuthEventEntity extends MapAuthEventEntity.AbstractAuthEventEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaAuthEventMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long timestamp; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long expiration; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set details = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaAuthEventEntity() { - this.metadata = new JpaAuthEventMetadata(); - } - - public JpaAuthEventEntity(final DeepCloner cloner) { - this.metadata = new JpaAuthEventMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_AUTH_EVENT; - } - - @Override - public Integer getEntityVersion() { - if (this.isMetadataInitialized()) return metadata.getEntityVersion(); - return this.entityVersion; - } - - @Override - public void setEntityVersion(final Integer entityVersion) { - this.metadata.setEntityVersion(entityVersion); - } - - @Override - public int getVersion() { - return this.version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(final String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public Long getExpiration() { - if (this.isMetadataInitialized()) return this.metadata.getExpiration(); - return this.expiration; - } - - @Override - public void setExpiration(final Long expiration) { - this.metadata.setExpiration(expiration); - } - - @Override - public Long getTimestamp() { - if (this.isMetadataInitialized()) return this.metadata.getTimestamp(); - return this.timestamp; - } - - @Override - public void setTimestamp(final Long timestamp) { - this.metadata.setTimestamp(timestamp); - } - - @Override - public String getClientId() { - return this.metadata.getClientId(); - } - - @Override - public void setClientId(final String clientId) { - this.metadata.setClientId(clientId); - } - - @Override - public Map getDetails() { - return this.details.stream().collect(Collectors.toMap(JpaAuthEventDetailEntity::getName, JpaAuthEventDetailEntity::getValue)); - } - - @Override - public void setDetails(final Map details) { - this.details.clear(); - if (details != null) { - details.forEach((key, value) -> this.details.add(new JpaAuthEventDetailEntity(this, key, value))); - } - } - - @Override - public String getError() { - return this.metadata.getError(); - } - - @Override - public void setError(final String error) { - this.metadata.setError(error); - } - - @Override - public String getIpAddress() { - return this.metadata.getIpAddress(); - } - - @Override - public void setIpAddress(final String ipAddress) { - this.metadata.setIpAddress(ipAddress); - } - - @Override - public String getRealmId() { - if (this.isMetadataInitialized()) return this.metadata.getRealmId(); - return this.realmId; - } - - @Override - public void setRealmId(final String realmId) { - this.metadata.setRealmId(realmId); - } - - @Override - public String getSessionId() { - return this.metadata.getSessionId(); - } - - @Override - public void setSessionId(final String sessionId) { - this.metadata.setSessionId(sessionId); - } - - @Override - public String getUserId() { - return this.metadata.getUserId(); - } - - @Override - public void setUserId(final String userId) { - this.metadata.setUserId(userId); - } - - @Override - public EventType getType() { - return this.metadata.getType(); - } - - @Override - public void setType(final EventType type) { - this.metadata.setType(type); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaAuthEventEntity)) return false; - return Objects.equals(getId(), ((JpaAuthEventEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventMetadata.java deleted file mode 100644 index 2545aed24ad..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/entity/JpaAuthEventMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.event.auth.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.events.MapAuthEventEntityImpl; - -/** - * Class that contains all the auth event metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaAuthEventMetadata extends MapAuthEventEntityImpl implements Serializable { - - public JpaAuthEventMetadata(final DeepCloner cloner) { - super(cloner); - } - - public JpaAuthEventMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapStorage.java deleted file mode 100644 index 92919e6c868..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapStorage.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.group; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.group.MapGroupEntityDelegate; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP; -import org.keycloak.models.map.storage.jpa.group.delegate.JpaGroupDelegateProvider; -import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; - -public class JpaGroupMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaGroupMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaGroupEntity.class, GroupModel.class, em); - } - - @Override - public Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaGroupEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("name"), - root.get("parentId") - ); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_GROUP); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaGroupModelCriteriaBuilder(); - } - - @Override - protected MapGroupEntity mapToEntityDelegate(JpaGroupEntity original) { - return new MapGroupEntityDelegate(new JpaGroupDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java deleted file mode 100644 index da6ce01a30e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.group; - -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import jakarta.persistence.criteria.CriteriaBuilder; - -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; -import org.keycloak.models.GroupModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupAttributeEntity; -import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.storage.SearchableModelField; - -public class JpaGroupModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaGroupModelCriteriaBuilder() { - super(JpaGroupModelCriteriaBuilder::new); - } - - private JpaGroupModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaGroupModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaGroupModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == GroupModel.SearchableFields.REALM_ID || - modelField == GroupModel.SearchableFields.NAME) { - validateValue(value, modelField, op, String.class); - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == GroupModel.SearchableFields.PARENT_ID) { - if (value.length == 1 && Objects.isNull(value[0])) { - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> - cb.isNull(root.get("parentId")) - ); - } - - validateValue(value, modelField, op, String.class); - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get("parentId"), value[0]) - ); - } else if (modelField == GroupModel.SearchableFields.ASSIGNED_ROLE) { - validateValue(value, modelField, op, String.class); - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> - cb.isTrue(cb.function("@>", - Boolean.TYPE, - cb.function("->", JsonbType.class, root.get("metadata"), cb.literal("fGrantedRoles")), - cb.literal(convertToJson(value[0])))) - ); - } else if (modelField == GroupModel.SearchableFields.ATTRIBUTE) { - validateValue(value, modelField, op, String.class, String.class); - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("attributes", JoinType.LEFT); - return cb.and( - cb.equal(join.get("name"), value[0]), - hashExpression(cb, join, "value_hash", value[1]), - cb.equal(join.get("value"), value[1]) - ); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case IN: - if (modelField == GroupModel.SearchableFields.ID) { - - Set uuids = getUuidsForInOperator(value, modelField); - - if (uuids.isEmpty()) return new JpaGroupModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> { - CriteriaBuilder.In in = cb.in(root.get("id")); - uuids.forEach(uuid -> in.value(uuid)); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case ILIKE: - if (modelField == GroupModel.SearchableFields.NAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case NOT_EXISTS: - if (modelField == GroupModel.SearchableFields.PARENT_ID) { - - return new JpaGroupModelCriteriaBuilder((cb, query, root) -> - cb.isNull(root.get("parentId")) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/delegate/JpaGroupDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/delegate/JpaGroupDelegateProvider.java deleted file mode 100644 index 946bbe6629f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/delegate/JpaGroupDelegateProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.group.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.group.MapGroupEntityFields; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity; - -public class JpaGroupDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaGroupDelegateProvider(JpaGroupEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapGroupEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapGroupEntityFields) { - switch ((MapGroupEntityFields) field) { - case ID: - case REALM_ID: - case NAME: - case PARENT_ID: - return getDelegate(); - - case ATTRIBUTES: - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaGroupEntity.class); - Root root = query.from(JpaGroupEntity.class); - root.fetch("attributes", JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - - setDelegate(em.createQuery(query).getSingleResult()); - break; - - default: - setDelegate(em.find(JpaGroupEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid group field: " + field); - } - } else { - setDelegate(em.find(JpaGroupEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupAttributeEntity.java deleted file mode 100644 index efa4dfd2aa5..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupAttributeEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.group.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -@Entity -@Table(name = "kc_group_attribute", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaGroupAttributeEntity extends JpaAttributeEntityWithHashValue { - - public JpaGroupAttributeEntity() { - } - - public JpaGroupAttributeEntity(JpaGroupEntity root, String name, String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupEntity.java deleted file mode 100644 index 43098e3bc8d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupEntity.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.group.entity; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.group.MapGroupEntity.AbstractGroupEntity; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_group", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "name", "parentId"})}) -public class JpaGroupEntity extends AbstractGroupEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaGroupMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String parentId; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaGroupEntity() { - this.metadata = new JpaGroupMetadata(); - } - - public JpaGroupEntity(DeepCloner cloner) { - this.metadata = new JpaGroupMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select group without metadata(json) field. - */ - public JpaGroupEntity(UUID id, int version, Integer entityVersion, String realmId, - String name, String parentId) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.name = name; - this.parentId = parentId; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_GROUP; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public void setParentId(String parentId) { - metadata.setParentId(parentId); - } - - @Override - public String getParentId() { - if (isMetadataInitialized()) return metadata.getParentId(); - return parentId; - } - - @Override - public Set getGrantedRoles() { - return metadata.getGrantedRoles(); - } - - @Override - public void setGrantedRoles(Set grantedRoles) { - metadata.setGrantedRoles(grantedRoles); - } - - @Override - public void addGrantedRole(String role) { - metadata.addGrantedRole(role); - } - - @Override - public void removeGrantedRole(String role) { - metadata.removeGrantedRole(role); - } - - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaGroupAttributeEntity attribute : attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - for (Map.Entry> entry : attributes.entrySet()) { - setAttribute(entry.getKey(), entry.getValue()); - } - } - } - - @Override - public List getAttribute(String name) { - return attributes.stream() - .filter(a -> Objects.equals(a.getName(), name)) - .map(JpaGroupAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public void setAttribute(String name, List values) { - removeAttribute(name); - for (String value : values) { - JpaGroupAttributeEntity attribute = new JpaGroupAttributeEntity(this, name, value); - attributes.add(attribute); - } - } - - @Override - public void removeAttribute(String name) { - attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaGroupEntity)) return false; - return Objects.equals(getId(), ((JpaGroupEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupMetadata.java deleted file mode 100644 index baa8025b593..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/entity/JpaGroupMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.group.entity; - -import java.io.Serializable; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.group.MapGroupEntityImpl; - -public class JpaGroupMetadata extends MapGroupEntityImpl implements Serializable { - - public JpaGroupMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaGroupMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/JpaMapExceptionConverter.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/JpaMapExceptionConverter.java deleted file mode 100644 index a111c0f01fd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/JpaMapExceptionConverter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2016 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.storage.jpa.hibernate; - -import org.keycloak.common.Profile; -import org.keycloak.models.map.storage.jpa.PersistenceExceptionConverter; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.ExceptionConverter; - -import jakarta.persistence.PersistenceException; - -/** - * This is needed for example by org.keycloak.transaction.JtaTransactionWrapper to map an exception - * that occurs on commit. - * - * @author Bill Burke - * @author Alexander Schwartz - */ -public class JpaMapExceptionConverter implements ExceptionConverter, EnvironmentDependentProviderFactory { - @Override - public Throwable convert(Throwable e) { - if (!(e instanceof PersistenceException)) return null; - return PersistenceExceptionConverter.convert(e); - } - - @Override - public String getId() { - return "jpa-map"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/contributor/JpaMapFunctionContributor.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/contributor/JpaMapFunctionContributor.java deleted file mode 100644 index 80a909144ea..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/contributor/JpaMapFunctionContributor.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.hibernate.contributor; - -import org.hibernate.boot.model.FunctionContributions; -import org.hibernate.boot.model.FunctionContributor; -import org.hibernate.dialect.CockroachDialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.type.BasicType; -import org.hibernate.type.BasicTypeReference; -import org.hibernate.type.SqlTypes; -import org.hibernate.type.StandardBasicTypes; -import org.jboss.logging.Logger; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - -/** - * A {@link FunctionContributor} to register custom functions. - */ -public class JpaMapFunctionContributor implements FunctionContributor { - - private final Logger LOG = Logger.getLogger(JpaMapFunctionContributor.class); - - @Override - public void contributeFunctions(FunctionContributions fc) { - - fc.getFunctionRegistry().registerPattern("->>", "?1->>?2", getBasicType(fc, StandardBasicTypes.STRING)); - fc.getFunctionRegistry().registerPattern("->", "?1->?2", getJsonbBasicType(fc)); - fc.getFunctionRegistry().registerPattern("@>", "?1@>?2::jsonb", getBasicType(fc, StandardBasicTypes.BOOLEAN)); - - contributeDbSpecificFunctions(fc); - } - - private BasicType getJsonbBasicType(FunctionContributions fc) { - return fc.getTypeConfiguration().getBasicTypeRegistry().resolve(JsonbType.class, SqlTypes.JSON); - } - - private BasicType getBasicType(FunctionContributions fc, BasicTypeReference btr) { - return fc.getTypeConfiguration().getBasicTypeRegistry().resolve(btr); - } - - private void contributeDbSpecificFunctions(FunctionContributions fc) { - Dialect dialect = fc.getDialect(); - if (dialect instanceof PostgreSQLDialect) { - fc.getFunctionRegistry().registerPattern("kc_hash", "sha256(?1::bytea)", getBasicType(fc, StandardBasicTypes.BINARY)); - } else if (dialect instanceof CockroachDialect) { - fc.getFunctionRegistry().registerPattern("kc_hash", "sha256(?1)", getBasicType(fc, StandardBasicTypes.STRING)); - } else { - LOG.warnf("Dialect %s not recognized.", dialect); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/contributor/JsonbMetadataBuilderContributor.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/contributor/JsonbMetadataBuilderContributor.java deleted file mode 100644 index f86a46dec8a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/contributor/JsonbMetadataBuilderContributor.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.hibernate.contributor; - -import org.hibernate.boot.MetadataBuilder; -import org.hibernate.boot.spi.MetadataBuilderContributor; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - -/** - * A {@link MetadataBuilderContributor} to register JSONB type. - */ -public class JsonbMetadataBuilderContributor implements MetadataBuilderContributor { - - @Override - public void contribute(MetadataBuilder metadataBuilder) { - metadataBuilder.applyBasicType(JsonbType.INSTANCE); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java deleted file mode 100644 index cd016489a1a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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.storage.jpa.hibernate.jsonb; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionMetadata; -import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionMetadata; -import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionMetadata; -import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyMetadata; -import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceMetadata; -import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeMetadata; -import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerMetadata; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata; -import org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeMetadata; -import org.keycloak.models.map.storage.jpa.event.admin.entity.JpaAdminEventMetadata; -import org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventMetadata; -import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupMetadata; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaAdminEventMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaAuthEventMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaAuthenticationSessionMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientSessionMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientScopeMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaComponentMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaGroupMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaPermissionMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaPolicyMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRealmMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaResourceMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaResourceServerMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaScopeMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaSingleUseObjectMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserConsentMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserFederatedIdentityMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserLoginFailureMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserMigration; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserSessionMigration; -import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureMetadata; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentMetadata; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata; -import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectMetadata; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentMetadata; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityMetadata; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserMetadata; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionMetadata; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionMetadata; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_POLICY; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_EVENT; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SESSION; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_CONSENT; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_SESSION; - -public class JpaEntityMigration { - - static final Map, BiFunction> MIGRATIONS = new HashMap<>(); - static { - //auth-sessions - MIGRATIONS.put(JpaAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaAuthenticationSessionMigration.MIGRATORS)); - MIGRATIONS.put(JpaRootAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaRootAuthenticationSessionMigration.MIGRATORS)); - //authorization - MIGRATIONS.put(JpaPermissionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION, tree, JpaPermissionMigration.MIGRATORS)); - MIGRATIONS.put(JpaPolicyMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_POLICY, tree, JpaPolicyMigration.MIGRATORS)); - MIGRATIONS.put(JpaResourceMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE, tree, JpaResourceMigration.MIGRATORS)); - MIGRATIONS.put(JpaResourceServerMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER, tree, JpaResourceServerMigration.MIGRATORS)); - MIGRATIONS.put(JpaScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE, tree, JpaScopeMigration.MIGRATORS)); - //clients - MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS)); - //client-scopes - MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS)); - //events - MIGRATIONS.put(JpaAdminEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ADMIN_EVENT, tree, JpaAdminEventMigration.MIGRATORS)); - MIGRATIONS.put(JpaAuthEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_EVENT, tree, JpaAuthEventMigration.MIGRATORS)); - //groups - MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS)); - //realms - MIGRATIONS.put(JpaComponentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaComponentMigration.MIGRATORS)); - MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS)); - //roles - MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS)); - //sessions - MIGRATIONS.put(JpaClientSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SESSION, tree, JpaClientSessionMigration.MIGRATORS)); - MIGRATIONS.put(JpaUserSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_SESSION, tree, JpaUserSessionMigration.MIGRATORS)); - //single-use-objects - MIGRATIONS.put(JpaSingleUseObjectMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT, tree, JpaSingleUseObjectMigration.MIGRATORS)); - //user-login-failures - MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE, tree, JpaUserLoginFailureMigration.MIGRATORS)); - //users - MIGRATIONS.put(JpaUserMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER, tree, JpaUserMigration.MIGRATORS)); - MIGRATIONS.put(JpaUserConsentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_CONSENT, tree, JpaUserConsentMigration.MIGRATORS)); - MIGRATIONS.put(JpaUserFederatedIdentityMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY, tree, JpaUserFederatedIdentityMigration.MIGRATORS)); - - } - - private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List> migrators) { - if (entityVersion > supportedVersion + 1) throw new IllegalArgumentException("Incompatible entity version: " + entityVersion + ", supportedVersion: " + supportedVersion); - - if (entityVersion < supportedVersion) { - while (entityVersion < supportedVersion) { - Function migrator = migrators.get(entityVersion); - if (migrator != null) { - node = migrator.apply(node); - } - entityVersion++; - } - } - return node; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java deleted file mode 100644 index eb76bffadb5..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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.storage.jpa.hibernate.jsonb; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -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.node.ObjectNode; -import java.io.IOException; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import org.hibernate.HibernateException; -import org.hibernate.type.descriptor.ValueBinder; -import org.hibernate.type.descriptor.ValueExtractor; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.AbstractJavaType; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.MutableMutabilityPlan; -import org.hibernate.type.descriptor.jdbc.BasicBinder; -import org.hibernate.type.descriptor.jdbc.BasicExtractor; -import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.usertype.DynamicParameterizedType; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.Serialization.IgnoreUpdatedMixIn; -import org.keycloak.models.map.common.Serialization.IgnoredTypeMixIn; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntityImpl; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntityImpl; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntityImpl; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntityImpl; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntityImpl; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntityImpl; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntityImpl; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntityImpl; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntityImpl; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntityImpl; -import org.keycloak.models.map.user.MapUserCredentialEntity; -import org.keycloak.models.map.user.MapUserCredentialEntityImpl; -import org.keycloak.util.EnumWithStableIndex; -import java.util.function.BiConsumer; -import org.hibernate.usertype.BaseUserTypeSupport; - -public class JsonbType extends BaseUserTypeSupport implements DynamicParameterizedType { - - public static final JsonbType INSTANCE = new JsonbType(); - public static final ObjectMapper MAPPER = new ObjectMapper() - .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) - .enable(SerializationFeature.INDENT_OUTPUT) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .activateDefaultTyping(new LaissezFaireSubTypeValidator(), ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY) - .registerModule(new SimpleModule().addAbstractTypeMapping(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl.class) - // realm abstract type mappings - .addAbstractTypeMapping(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl.class) - .addAbstractTypeMapping(MapAuthenticationFlowEntity.class, MapAuthenticationFlowEntityImpl.class) - .addAbstractTypeMapping(MapAuthenticatorConfigEntity.class, MapAuthenticatorConfigEntityImpl.class) - .addAbstractTypeMapping(MapClientInitialAccessEntity.class, MapClientInitialAccessEntityImpl.class) - .addAbstractTypeMapping(MapIdentityProviderEntity.class, MapIdentityProviderEntityImpl.class) - .addAbstractTypeMapping(MapIdentityProviderMapperEntity.class, MapIdentityProviderMapperEntityImpl.class) - .addAbstractTypeMapping(MapOTPPolicyEntity.class, MapOTPPolicyEntityImpl.class) - .addAbstractTypeMapping(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl.class) - .addAbstractTypeMapping(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl.class) - .addAbstractTypeMapping(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl.class) - // user abstract type mappings - .addAbstractTypeMapping(MapUserCredentialEntity.class, MapUserCredentialEntityImpl.class)) - .addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class) - .addMixIn(DeepCloner.class, IgnoredTypeMixIn.class) - .addMixIn(EntityWithAttributes.class, IgnoredMetadataFieldsMixIn.class) - .addMixIn(EnumWithStableIndex.class, EnumsMixIn.class) - ; - abstract class IgnoredMetadataFieldsMixIn { - @JsonIgnore public abstract String getId(); - @JsonIgnore public abstract Map> getAttributes(); - - // roles: assumed it's true when getClient() != null, see AbstractRoleEntity.isClientRole() - @JsonIgnore public abstract Boolean isClientRole(); - } - - abstract static class EnumsMixIn implements EnumWithStableIndex { - - // we convert enums to its index and vice versa - @Override - @JsonValue public abstract int getStableIndex(); - } - - private Class valueType; - - @Override - @SuppressWarnings("unchecked") - protected void resolve(BiConsumer resolutionConsumer) { - resolutionConsumer.accept(new JsonbJavaTypeDescriptor(), JsonbSqlTypeDescriptor.INSTANCE); - } - - @Override - public void setParameterValues(Properties parameters) { - this.valueType = ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass(); - } - - private static class JsonbSqlTypeDescriptor implements JdbcType { - - private static final JsonbSqlTypeDescriptor INSTANCE = new JsonbSqlTypeDescriptor(); - - @Override - public int getJdbcTypeCode() { - return Types.OTHER; - } - - @Override - public ValueBinder getBinder(JavaType javaTypeDescriptor) { - return new BasicBinder(javaTypeDescriptor, this) { - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setObject(index, javaTypeDescriptor.unwrap(value, JsonNode.class, options), getJdbcTypeCode()); - } - - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { - st.setObject(name, javaTypeDescriptor.unwrap(value, JsonNode.class, options), getJdbcTypeCode()); - } - }; - } - - @Override - public ValueExtractor getExtractor(JavaType javaTypeDescriptor) { - return new BasicExtractor(javaTypeDescriptor, this) { - @Override - protected X doExtract(ResultSet rs, int index, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap(extractJson(rs, index), options); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap(extractJson(statement, index), options); - } - - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap(extractJson(statement, name), options); - } - }; - } - - private Object extractJson(ResultSet rs, int index) throws SQLException { - return rs.getObject(index); - } - - private Object extractJson(CallableStatement statement, int index) throws SQLException { - return statement.getObject(index); - } - - private Object extractJson(CallableStatement statement, String name) throws SQLException { - return statement.getObject(name); - } - } - - private class JsonbJavaTypeDescriptor extends AbstractJavaType { - - public JsonbJavaTypeDescriptor() { - super(Object.class, new MutableMutabilityPlan() { - @Override - protected Object deepCopyNotNull(Object value) { - try { - return MAPPER.readValue(MAPPER.writerFor(value.getClass()).writeValueAsBytes(value), value.getClass()); - } catch (IOException e) { - throw new HibernateException("unable to deep copy object", e); - } - } - }); - } - - @Override - @SuppressWarnings("unchecked") - public Object fromString(CharSequence json) { - try { - ObjectNode tree = MAPPER.readValue(json.toString(), ObjectNode.class); - JsonNode ev = tree.get("entityVersion"); - if (ev == null || ! ev.isInt()) throw new IllegalArgumentException("unable to read entity version from " + json); - - Integer entityVersion = ev.asInt(); - - tree = migrate(tree, entityVersion); - - return MAPPER.treeToValue(tree, valueType); - } catch (IOException e) { - throw new HibernateException("unable to read", e); - } - } - - private ObjectNode migrate(ObjectNode tree, Integer entityVersion) { - return JpaEntityMigration.MIGRATIONS.getOrDefault(valueType, (node, version) -> node).apply(tree, entityVersion); - } - - @Override - @SuppressWarnings("unchecked") - public X unwrap(Object value, Class type, WrapperOptions options) { - if (value == null) return null; - - String stringValue = (value instanceof String) ? (String) value : toString(value); - try { - return (X) MAPPER.readTree(stringValue); - } catch (IOException e) { - throw new HibernateException("unable to read", e); - } - } - - @Override - public Object wrap(X value, WrapperOptions options) { - if (value == null) return null; - - return fromString(value.toString()); - } - - @Override - public String toString(Object value) { - try { - return MAPPER.writeValueAsString(value); - } catch (IOException e) { - throw new HibernateException("unable to transform value: " + value + " as String.", e); - } - } - - @Override - public boolean areEqual(Object one, Object another) { - if (one == another) return true; - return Objects.equals(one, another); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAdminEventMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAdminEventMigration.java deleted file mode 100644 index b56d3ce38a9..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAdminEventMigration.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -/** - * Migration functions for admin events. - * - * @author Stefan Guilhen - */ -public class JpaAdminEventMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} \ No newline at end of file diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAuthEventMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAuthEventMigration.java deleted file mode 100644 index d11974df742..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAuthEventMigration.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -/** - * Migration functions for authentication events. - * - * @author Stefan Guilhen - */ -public class JpaAuthEventMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} \ No newline at end of file diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAuthenticationSessionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAuthenticationSessionMigration.java deleted file mode 100644 index 19564da9b85..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaAuthenticationSessionMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaAuthenticationSessionMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientMigration.java deleted file mode 100644 index 349e84ce22e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaClientMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientScopeMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientScopeMigration.java deleted file mode 100644 index e5293f2b12e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientScopeMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaClientScopeMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientSessionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientSessionMigration.java deleted file mode 100644 index 627f843bc27..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientSessionMigration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -public class JpaClientSessionMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaComponentMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaComponentMigration.java deleted file mode 100644 index 1602aa97370..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaComponentMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Migration functions for components. - * - * @author Stefan Guilhen - */ -public class JpaComponentMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaGroupMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaGroupMigration.java deleted file mode 100644 index cb4fa7aba77..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaGroupMigration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaGroupMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaPermissionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaPermissionMigration.java deleted file mode 100644 index 5cd6e524d0f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaPermissionMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaPermissionMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaPolicyMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaPolicyMigration.java deleted file mode 100644 index 262aa876598..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaPolicyMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaPolicyMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRealmMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRealmMigration.java deleted file mode 100644 index 150b95baf8f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRealmMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Migration functions for realms. - * - * @author Stefan Guilhen - */ -public class JpaRealmMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaResourceMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaResourceMigration.java deleted file mode 100644 index 41e2059725d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaResourceMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaResourceMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaResourceServerMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaResourceServerMigration.java deleted file mode 100644 index 23ad5288a6a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaResourceServerMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaResourceServerMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRoleMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRoleMigration.java deleted file mode 100644 index 330d0153ef5..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRoleMigration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaRoleMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRootAuthenticationSessionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRootAuthenticationSessionMigration.java deleted file mode 100644 index b4a5ccb6d96..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaRootAuthenticationSessionMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaRootAuthenticationSessionMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaScopeMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaScopeMigration.java deleted file mode 100644 index b6b68e2a1f1..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaScopeMigration.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class JpaScopeMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaSingleUseObjectMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaSingleUseObjectMigration.java deleted file mode 100644 index 1afe80a3c30..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaSingleUseObjectMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Migration functions for single-use objects. - * - * @author Stefan Guilhen - */ -public class JpaSingleUseObjectMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} \ No newline at end of file diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserConsentMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserConsentMigration.java deleted file mode 100644 index b9fe9bb42c1..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserConsentMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Migration functions for user consents. - * - * @author Stefan Guilhen - */ -public class JpaUserConsentMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserFederatedIdentityMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserFederatedIdentityMigration.java deleted file mode 100644 index 342ebbca192..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserFederatedIdentityMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Migration functions for user federated identities. - * - * @author Stefan Guilhen - */ -public class JpaUserFederatedIdentityMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserLoginFailureMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserLoginFailureMigration.java deleted file mode 100644 index 839432cd086..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserLoginFailureMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Migration functions for user login failures. - * - * @author Stefan Guilhen - */ -public class JpaUserLoginFailureMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserMigration.java deleted file mode 100644 index 692f45e4210..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserMigration.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -/** - * Migration functions for users. - * - * @author Stefan Guilhen - */ -public class JpaUserMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o, - JpaUserMigration::migrateTreeFrom1To2 - ); - - // adds a usernameWithCase column into json - private static ObjectNode migrateTreeFrom1To2(ObjectNode node) { - JsonNode usernameNode = node.path("fUsername"); - return node.put("usernameWithCase", usernameNode.asText()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserSessionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserSessionMigration.java deleted file mode 100644 index 8c856c20291..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserSessionMigration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.hibernate.jsonb.migration; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -public class JpaUserSessionMigration { - - public static final List> MIGRATORS = Arrays.asList( - o -> o // no migration yet - ); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaAutoFlushListener.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaAutoFlushListener.java deleted file mode 100644 index 0bbc2ea2c44..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaAutoFlushListener.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2022. 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.storage.jpa.hibernate.listeners; - -import org.hibernate.FlushMode; -import org.hibernate.HibernateException; -import org.hibernate.engine.spi.ActionQueue; -import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.SessionEventListenerManager; -import org.hibernate.event.internal.DefaultAutoFlushEventListener; -import org.hibernate.event.spi.AutoFlushEvent; -import org.hibernate.event.spi.EventSource; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.stat.spi.StatisticsImplementor; -import org.jboss.logging.Logger; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; - -/** - * Extends Hibernate's {@link DefaultAutoFlushEventListener} to always flush queued inserts to allow correct handling - * of orphans of that entities in the same transactions, and also to clear a session-level query cache. - *

- * If they wouldn't be flushed, they won't be orphaned (at least not in Hibernate 5.3.24.Final). - * This class copies over all functionality of the base class that can't be overwritten via inheritance. - * This is being tracked as part of keycloak/keycloak#11666. - *

- * This also clears the JPA map store query level cache for the {@link JpaMapStorage} whenever there is some data written to the database. - */ -public class JpaAutoFlushListener extends DefaultAutoFlushEventListener { - - public static final JpaAutoFlushListener INSTANCE = new JpaAutoFlushListener(); - - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, DefaultAutoFlushEventListener.class.getName()); - - public void onAutoFlush(AutoFlushEvent event) throws HibernateException { - final EventSource source = event.getSession(); - final SessionEventListenerManager eventListenerManager = source.getEventListenerManager(); - try { - eventListenerManager.partialFlushStart(); - - if (flushMightBeNeeded(source)) { - // Need to get the number of collection removals before flushing to executions - // (because flushing to executions can add collection removal actions to the action queue). - final ActionQueue actionQueue = source.getActionQueue(); - final int oldSize = actionQueue.numberOfCollectionRemovals(); - flushEverythingToExecutions(event); - if (flushIsReallyNeeded(event, source)) { - LOG.trace("Need to execute flush"); - event.setFlushRequired(true); - - // note: performExecutions() clears all collectionXxxxtion - // collections (the collection actions) in the session - performExecutions(source); - postFlush(source); - - postPostFlush(source); - - final StatisticsImplementor statistics = source.getFactory().getStatistics(); - if (statistics.isStatisticsEnabled()) { - statistics.flush(); - } - } else { - LOG.trace("No need to execute flush"); - event.setFlushRequired(false); - actionQueue.clearFromFlushNeededCheck(oldSize); - } - } - } finally { - eventListenerManager.partialFlushEnd( - event.getNumberOfEntitiesProcessed(), - event.getNumberOfEntitiesProcessed() - ); - } - } - - private boolean flushIsReallyNeeded(AutoFlushEvent event, final EventSource source) { - boolean flushIsReallyNeeded = source.getHibernateFlushMode() == FlushMode.ALWAYS - // START OF FIX for auto-flush-mode on inserts that might later be deleted in the same transaction - || source.getActionQueue().numberOfInsertions() > 0 - // END OF FIX - || source.getActionQueue().areTablesToBeUpdated(event.getQuerySpaces()); - // START OF HOOK: clear query cache when data is flushed to the database - if (flushIsReallyNeeded) { - // clear the per-session query cache, as changing an entity might change any of the cached query results - JpaMapStorage.clearQueryCache(source.getSession()); - } - // END OF HOOK - return flushIsReallyNeeded; - } - - private boolean flushMightBeNeeded(final EventSource source) { - final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); - return !source.getHibernateFlushMode().lessThan(FlushMode.AUTO) - && (persistenceContext.getNumberOfManagedEntities() > 0 - || persistenceContext.getCollectionEntriesSize() > 0); - } - -} \ No newline at end of file diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaEntityVersionListener.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaEntityVersionListener.java deleted file mode 100644 index 7362f637428..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaEntityVersionListener.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2022. 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.storage.jpa.hibernate.listeners; - -import org.hibernate.HibernateException; -import org.hibernate.event.spi.PreDeleteEvent; -import org.hibernate.event.spi.PreDeleteEventListener; -import org.hibernate.event.spi.PreInsertEvent; -import org.hibernate.event.spi.PreInsertEventListener; -import org.hibernate.event.spi.PreUpdateEvent; -import org.hibernate.event.spi.PreUpdateEventListener; -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; - -/** - * Listen on changes on child- and root entities and updates the current entity version of the root. - * - * This support a multiple level parent-child relationship, where all parents needs the entity version to be updated - * in case it was effectively changed. The traversing is stopped at that point when it is detected that parent entity - * version is the same one. - * - * It is based on an assumption that it may happen that one parent entity could be extracted into "parent A -> parent B -> child" - * format. Then the change (insertion, deletion or update) of the child should bump the entity version of both parent B and parent A. - */ -public class JpaEntityVersionListener implements PreInsertEventListener, PreDeleteEventListener, PreUpdateEventListener { - - public static final JpaEntityVersionListener INSTANCE = new JpaEntityVersionListener(); - - /** - * Traverse from current entity through its parent tree and update the entity version of it. - * Stop if non-changed parent is found. - */ - public void updateEntityVersion(Object entity) throws HibernateException { - Object root = entity; - if (root instanceof JpaChildEntity) { - while (root instanceof JpaChildEntity) { - root = ((JpaChildEntity) entity).getParent(); - if (root instanceof JpaRootEntity) { - if (!((JpaRootEntity) root).updateEntityVersion()) { - return; - } - } - } - } else if (root instanceof JpaRootEntity) { - ((JpaRootEntity) root).updateEntityVersion(); - } - } - - @Override - public boolean onPreInsert(PreInsertEvent event) { - updateEntityVersion(event.getEntity()); - return false; - } - - @Override - public boolean onPreDelete(PreDeleteEvent event) { - updateEntityVersion(event.getEntity()); - return false; - } - - @Override - public boolean onPreUpdate(PreUpdateEvent event) { - updateEntityVersion(event.getEntity()); - return false; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java deleted file mode 100644 index 5ed0400c73e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2022. 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.storage.jpa.hibernate.listeners; - -import org.hibernate.HibernateException; -import org.hibernate.Session; -import org.hibernate.event.spi.PreDeleteEvent; -import org.hibernate.event.spi.PreDeleteEventListener; -import org.hibernate.event.spi.PreInsertEvent; -import org.hibernate.event.spi.PreInsertEventListener; -import org.hibernate.event.spi.PreUpdateEvent; -import org.hibernate.event.spi.PreUpdateEventListener; -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import jakarta.persistence.LockModeType; -import java.util.Objects; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; - -/** - * Listen on changes on child entities and forces an optimistic locking increment on the closest parent aka root. - * The assumption is that any parent of a child entity is root entity. Optimistic locking is enforced on child entity - * which is not the child entity at the same time. This prevents {@link jakarta.persistence.OptimisticLockException}s - * when different children in the same parent are being manipulated at the same time by different threads. - * - * This support a multiple level parent-child relationship, where only the closest parent is locked. - */ -public class JpaOptimisticLockingListener implements PreInsertEventListener, PreDeleteEventListener, PreUpdateEventListener { - - public static final JpaOptimisticLockingListener INSTANCE = new JpaOptimisticLockingListener(); - - /** - * Check if the entity is a child with a parent and force optimistic locking increment on the parent aka root. - */ - public void lockRootEntity(Session session, Object entity) throws HibernateException { - if (entity instanceof JpaChildEntity && ! (entity instanceof JpaRootEntity)) { - Object root = ((JpaChildEntity) entity).getParent(); - Objects.requireNonNull(root, "children must always return their parent, never null"); - - // do not lock if root doesn't implement implicit optimistic locking mechanism - if (! (root instanceof JpaRootVersionedEntity)) return; - - // a session would not contain the entity if it has been deleted - // if the entity has been deleted JPA would throw an IllegalArgumentException with the message - // "entity not in the persistence context". - if (session.contains(root)) { - session.lock(root, LockModeType.OPTIMISTIC_FORCE_INCREMENT); - } - } - } - - @Override - public boolean onPreInsert(PreInsertEvent event) { - lockRootEntity(event.getSession(), event.getEntity()); - return false; - } - - @Override - public boolean onPreDelete(PreDeleteEvent event) { - lockRootEntity(event.getSession(), event.getEntity()); - return false; - } - - @Override - public boolean onPreUpdate(PreUpdateEvent event) { - lockRootEntity(event.getSession(), event.getEntity()); - return false; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/DefaultLiquibaseConnectionProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/DefaultLiquibaseConnectionProvider.java deleted file mode 100644 index db026831a2a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/DefaultLiquibaseConnectionProvider.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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.storage.jpa.liquibase.connection; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import liquibase.Liquibase; -import liquibase.Scope; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.exception.LiquibaseException; -import liquibase.exception.ServiceNotFoundException; -import liquibase.resource.ClassLoaderResourceAccessor; -import liquibase.resource.ResourceAccessor; -import liquibase.servicelocator.ServiceLocator; -import liquibase.sqlgenerator.SqlGeneratorFactory; -import liquibase.ui.LoggerUIService; -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; - -/** - * A {@link MapLiquibaseConnectionProvider} implementation for the map-jpa module. This provider registers the custom {@code Liquibase} - * changes and data types that were developed to better support working with data stored as JSON in the database. - *

- * An instance of this provider can be obtained via {@link KeycloakSession#getProvider(Class)} as follows: - *
- *     MapLiquibaseConnectionProvider liquibaseProvider = session.getProvider(MapLiquibaseConnectionProvider.class);
- * 
- * - * @author Stefan Guilhen - */ -public class DefaultLiquibaseConnectionProvider implements MapLiquibaseConnectionProvider { - - private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class); - private static final String KEYCLOAK_JPA_LEGACY_MODULE = "org.keycloak.connections.jpa"; - - @SuppressWarnings("unused") - public DefaultLiquibaseConnectionProvider(final KeycloakSession session) { - } - - @Override - public void close() { - } - - @Override - public Liquibase getLiquibaseForCustomUpdate(final Connection connection, final String defaultSchema, final String changelogLocation, - final ClassLoader classloader, final String changelogTableName) throws LiquibaseException { - - String scopeId = enterLiquibaseScope(); - try { - // This acts on the unwrapped database connection as Liquibase will commit and rollback the transaction as needed. - // Otherwise, the connection will not recover from an SQL error when running for example on a PostgreSQL database. - // This was needed when adding support for JTA - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnectionFromPool(connection.unwrap(Connection.class))); - if (defaultSchema != null) { - database.setDefaultSchemaName(defaultSchema); - } - ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classloader); - database.setDatabaseChangeLogTableName(changelogTableName); - - logger.debugf("Using changelog file %s and changelogTableName %s", changelogLocation, database.getDatabaseChangeLogTableName()); - - return new Liquibase(changelogLocation, resourceAccessor, database) { - @Override - public void close() throws LiquibaseException { - super.close(); - exitLiquibaseScope(scopeId); - } - }; - } catch (LiquibaseException | RuntimeException ex) { - // When this trows an exception, close the scope here. - // If it returns the Liquibase object, the scope will be closed once the Liquibase object is being closed. - exitLiquibaseScope(scopeId); - throw ex; - } catch (SQLException e) { - exitLiquibaseScope(scopeId); - throw new LiquibaseException(e); - } - } - - private void exitLiquibaseScope(String scopeId) { - try { - Scope.exit(scopeId); - } catch (Exception e) { - throw new RuntimeException("Failed to exist scope: " + e.getMessage(), e); - } finally { - // To avoid cached instances from the just closed scope. - // Must be called only after exiting the scope. - SqlGeneratorFactory.reset(); - } - } - - private String enterLiquibaseScope() { - String scopeId; - final Map scopeValues = new HashMap<>(); - // Setting the LoggerUIService here prevents Liquibase from logging each change set to the console using java.util.Logging in the Quarkus setup - scopeValues.put(Scope.Attr.ui.name(), new LoggerUIService()); - - // This uses the same serviceloader as the parent (that is replaced in Keycloak Quarkus) - // and prevents any class from the legacy JPA store to leak into the service discovery. - // This can be removed once the legacy JPA provider is no longer around in both the - // Wildfly and Quarkus distributions of Keycloak. - ServiceLocator serviceLocator = Scope.getCurrentScope().getServiceLocator(); - scopeValues.put(Scope.Attr.serviceLocator.name(), new ServiceLocator() { - @Override - public int getPriority() { - return serviceLocator.getPriority(); - } - - @Override - public List findInstances(Class interfaceType) throws ServiceNotFoundException { - List instances = serviceLocator.findInstances(interfaceType); - // prevent any class from the legacy JPA store to leak into the service discovery - instances = instances.stream().filter(t -> !t.getClass().getPackage().getName().startsWith(KEYCLOAK_JPA_LEGACY_MODULE)).collect(Collectors.toList()); - return instances; - } - }); - try { - scopeId = Scope.enter(scopeValues); - } catch (Exception e) { - throw new RuntimeException("Failed to initialize Liquibase: " + e.getMessage(), e); - } finally { - // To avoid cached instances from another scope. - // Must be called only after entering the scope. - SqlGeneratorFactory.reset(); - } - return scopeId; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/DefaultLiquibaseConnectionProviderFactory.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/DefaultLiquibaseConnectionProviderFactory.java deleted file mode 100644 index d8fc50b7c69..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/DefaultLiquibaseConnectionProviderFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.storage.jpa.liquibase.connection; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -/** - * {@link MapLiquibaseConnectionProviderFactory} implementation for the map-jpa module. It produces an instance of - * {@link DefaultLiquibaseConnectionProvider} when {@link #create(KeycloakSession)} is called. - * - * @author Stefan Guilhen - */ -public class DefaultLiquibaseConnectionProviderFactory implements MapLiquibaseConnectionProviderFactory { - - public static final String PROVIDER_ID = "default"; - - @Override - public MapLiquibaseConnectionProvider create(KeycloakSession session) { - return new DefaultLiquibaseConnectionProvider(session); - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - } - - @Override - public String getId() { - return PROVIDER_ID; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/JdbcConnectionFromPool.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/JdbcConnectionFromPool.java deleted file mode 100644 index 3679d60eef1..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/JdbcConnectionFromPool.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.liquibase.connection; - -import liquibase.database.jvm.JdbcConnection; -import liquibase.exception.DatabaseException; - -import java.sql.Connection; - -/** - * Wrapper for JDBC connections retrieved from a connection pool. - * Such a connection would not be closed, but used within the current transaction context. - * - * @author Alexander Schwartz - */ -public class JdbcConnectionFromPool extends JdbcConnection { - public JdbcConnectionFromPool(Connection connection) { - super(connection); - } - - @Override - public void close() throws DatabaseException { - rollback(); - // do not close the connection here, as connection will be returned to the pool or continued to be used in this session - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionProvider.java deleted file mode 100644 index 680eb3ec827..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.storage.jpa.liquibase.connection; - -import java.sql.Connection; - -import liquibase.Liquibase; -import liquibase.exception.LiquibaseException; -import org.keycloak.provider.Provider; - -public interface MapLiquibaseConnectionProvider extends Provider { - - Liquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException; -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionProviderFactory.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionProviderFactory.java deleted file mode 100644 index a25bf3679c6..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionProviderFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.storage.jpa.liquibase.connection; - -import org.keycloak.provider.ProviderFactory; - -public interface MapLiquibaseConnectionProviderFactory extends ProviderFactory { -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionSpi.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionSpi.java deleted file mode 100644 index be518d90fc3..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/connection/MapLiquibaseConnectionSpi.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.storage.jpa.liquibase.connection; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class MapLiquibaseConnectionSpi implements Spi { - - public final static String SPI_NAME = "mapLiquibaseConnection"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return SPI_NAME; - } - - @Override - public Class getProviderClass() { - return MapLiquibaseConnectionProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return MapLiquibaseConnectionProviderFactory.class; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/AddGeneratedColumnConfig.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/AddGeneratedColumnConfig.java deleted file mode 100644 index 6882f9c0149..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/AddGeneratedColumnConfig.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import liquibase.change.AddColumnConfig; -import liquibase.parser.core.ParsedNode; -import liquibase.parser.core.ParsedNodeException; -import liquibase.resource.ResourceAccessor; - -/** - * A {@link liquibase.change.ColumnConfig} extension that contains attributes either to specify - * - a JSON column and the property to be selected from the JSON file - * - a hashOf property with column name to be used for the generating a column with hash value of it. - * - * @author Stefan Guilhen - */ -public class AddGeneratedColumnConfig extends AddColumnConfig { - - private String jsonColumn; - private String jsonProperty; - private String hashOf; - - /** - * Obtains the name of the column that contains JSON files. - * - * @return the JSON column name. - */ - public String getJsonColumn() { - return this.jsonColumn; - } - - /** - * Sets the name of the column that contains JSON files. - * - * @param jsonColumn the name of the JSON column. - */ - public void setJsonColumn(final String jsonColumn) { - this.jsonColumn = jsonColumn; - } - - /** - * Obtains the name of the property inside the JSON file. - * - * @return the name of the JSON property. - */ - public String getJsonProperty() { - return this.jsonProperty; - } - - /** - * Sets the name of the property inside the JSON file. - * - * @param jsonProperty the name of the JSON property. - */ - public void setJsonProperty(final String jsonProperty) { - this.jsonProperty = jsonProperty; - } - - /** - * Obtains the column name to be used for the generating a column with hash value of it. - * - * @return the name of the column - */ - public String getHashOf() { - return hashOf; - } - - /** - * Sets the column name to be used for the generating a column with hash value of it. - * - * @param hashOf the column name for hash - */ - public void setHashOf(String hashOf) { - this.hashOf = hashOf; - } - - @Override - public void load(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException { - // load the standard column attributs and then load the JSON attributes. - super.load(parsedNode, resourceAccessor); - this.jsonColumn = parsedNode.getChildValue(null, "jsonColumn", String.class); - this.jsonProperty = parsedNode.getChildValue(null, "jsonProperty", String.class); - this.hashOf = parsedNode.getChildValue(null, "hashOf", String.class); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexChange.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexChange.java deleted file mode 100644 index 4a6ed3dcc6b..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexChange.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import java.util.List; -import java.util.stream.Collectors; - -import liquibase.change.AbstractChange; -import liquibase.change.Change; -import liquibase.change.ChangeMetaData; -import liquibase.change.ChangeStatus; -import liquibase.change.ChangeWithColumns; -import liquibase.change.DatabaseChange; -import liquibase.change.DatabaseChangeProperty; -import liquibase.change.core.CreateIndexChange; -import liquibase.change.core.DropIndexChange; -import liquibase.database.Database; -import liquibase.statement.SqlStatement; - -/** - * Extension used to create an index for properties of JSON files stored in the database. Some databases, like {@code Postgres}, - * have native support for these indexes while other databases may require different constructs to achieve this (like creation - * of a separate column based on the JSON property and subsequent indexing of that column). - *

- * Example configuration in the changelog: - *

- *     <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- *                    xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
- *                    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
- *                    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
- *                    http://www.liquibase.org/xml/ns/dbchangelog-ext
- *                    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
- *
- *     <changeSet author="keycloak" id="some_id">
- *         ...
- *         <ext:createJsonIndex tableName="test" indexName="some_index_name">
- *             <ext:column jsonColumn="metadata" jsonProperty="name"/>
- *         </ext:createJsonIndex>
- *     </changeSet>
- * 
- * The above configuration is creating an inverted (GIN) index for the {@code name} property of JSON files stored in column - * {@code metadata} in table {@code test}. - *

- * The {@code jsonProperty} is optional - when it is absent the index will be created for the whole JSON. - * - * @author Stefan Guilhen - */ -@DatabaseChange(name="createJsonIndex", description = "Creates an index for one or more JSON properties", - priority = ChangeMetaData.PRIORITY_DEFAULT, appliesTo = "index") -public class CreateJsonIndexChange extends AbstractChange implements ChangeWithColumns { - - private final CreateIndexChange delegate; - - public CreateJsonIndexChange() { - this.delegate = new CreateIndexChange(); - } - - @DatabaseChangeProperty - public String getCatalogName() { - return delegate.getCatalogName(); - } - - public void setCatalogName(String catalogName) { - this.delegate.setCatalogName(catalogName); - } - - @DatabaseChangeProperty(mustEqualExisting ="index.schema") - public String getSchemaName() { - return delegate.getSchemaName(); - } - - public void setSchemaName(String schemaName) { - this.delegate.setSchemaName(schemaName); - } - - @DatabaseChangeProperty(mustEqualExisting = "index.table", description = "Name of the table to add the index to", exampleValue = "person") - public String getTableName() { - return this.delegate.getTableName(); - } - - public void setTableName(String tableName) { - this.delegate.setTableName(tableName); - } - - @DatabaseChangeProperty(mustEqualExisting = "index", description = "Name of the index to create") - public String getIndexName() { - return this.delegate.getIndexName(); - } - - public void setIndexName(String indexName) { - this.delegate.setIndexName(indexName); - } - - @DatabaseChangeProperty(description = "Tablepace to create the index in.") - public String getTablespace() { - return this.delegate.getTablespace(); - } - - public void setTablespace(String tablespace) { - this.delegate.setTablespace(tablespace); - } - - @DatabaseChangeProperty(description = "Unique values index", since = "1.8") - public Boolean isUnique() { - return this.delegate.isUnique(); - } - - public void setUnique(Boolean isUnique) { - this.delegate.setUnique(isUnique); - } - - @DatabaseChangeProperty(isChangeProperty = false) - public String getAssociatedWith() { - return delegate.getAssociatedWith(); - } - - public void setAssociatedWith(String associatedWith) { - this.delegate.setAssociatedWith(associatedWith); - } - - @DatabaseChangeProperty - public Boolean getClustered() { - return this.delegate.getClustered(); - } - - public void setClustered(Boolean clustered) { - this.delegate.setClustered(clustered); - } - - @Override - public void addColumn(AddGeneratedColumnConfig column) { - delegate.addColumn(column); - } - - @Override - @DatabaseChangeProperty(mustEqualExisting = "index.column", description = "Column(s) to add to the index", requiredForDatabase = "all") - public List getColumns() { - return this.delegate.getColumns().stream().map(AddGeneratedColumnConfig.class::cast).collect(Collectors.toList()); - } - - @Override - public void setColumns(List columns) { - columns.forEach(this.delegate::addColumn); - } - - @Override - public String getConfirmationMessage() { - return delegate.getConfirmationMessage(); - } - - @Override - public SqlStatement[] generateStatements(Database database) { - return new SqlStatement[]{new CreateJsonIndexStatement(this.getCatalogName(), this.getSchemaName(), this.getTableName(), - this.getIndexName(), this.isUnique(), this.getAssociatedWith(), this.getTablespace(), this.getClustered(), - this.getColumns().toArray(new AddGeneratedColumnConfig[0]))}; - } - - @Override - protected Change[] createInverses() { - DropIndexChange inverse = new DropIndexChange(); - inverse.setSchemaName(getSchemaName()); - inverse.setTableName(getTableName()); - inverse.setIndexName(getIndexName()); - return new Change[]{inverse}; - } - - @Override - public ChangeStatus checkStatus(Database database) { - return delegate.checkStatus(database); - } - - @Override - public String getSerializedObjectNamespace() { - return GENERIC_CHANGELOG_EXTENSION_NAMESPACE; - } - - @Override - public Object getSerializableFieldValue(String field) { - return delegate.getSerializableFieldValue(field); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexGenerator.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexGenerator.java deleted file mode 100644 index d37d2d433a9..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexGenerator.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import java.util.Arrays; -import java.util.stream.Collectors; - -import liquibase.database.Database; -import liquibase.database.core.CockroachDatabase; -import liquibase.database.core.PostgresDatabase; -import liquibase.exception.ValidationErrors; -import liquibase.sql.Sql; -import liquibase.sql.UnparsedSql; -import liquibase.sqlgenerator.SqlGenerator; -import liquibase.sqlgenerator.SqlGeneratorChain; -import liquibase.sqlgenerator.core.AbstractSqlGenerator; -import liquibase.statement.core.CreateIndexStatement; -import liquibase.structure.core.Index; -import liquibase.structure.core.Table; -import liquibase.util.StringUtil; - -/** - * A {@link SqlGenerator} implementation that supports {@link CreateJsonIndexStatement}s. It generates the SQL required - * to create an index for properties of JSON files stored in one of the table columns. - * - * @author Stefan Guilhen - */ -public class CreateJsonIndexGenerator extends AbstractSqlGenerator { - - /** - * Override the priority. This is needed because {@link CreateJsonIndexStatement} is a subtype of {@link CreateIndexStatement} - * and is thus a match for the standard index generators. By increasing the priority we ensure this is processed before - * the other generators. - * - * @return this generator's priority. - */ - @Override - public int getPriority() { - return SqlGenerator.PRIORITY_DATABASE + 1; - } - - @Override - public ValidationErrors validate(CreateJsonIndexStatement createIndexStatement, Database database, SqlGeneratorChain sqlGeneratorChain) { - ValidationErrors validationErrors = new ValidationErrors(); - validationErrors.checkRequiredField("tableName", createIndexStatement.getTableName()); - validationErrors.checkRequiredField("columns", createIndexStatement.getColumns()); - Arrays.stream(createIndexStatement.getColumns()).map(AddGeneratedColumnConfig.class::cast) - .forEach(config -> { - validationErrors.checkRequiredField("jsonColumn", config.getJsonColumn()); - }); - return validationErrors; - } - - @Override - public Sql[] generateSql(CreateJsonIndexStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) { - - if (!(database instanceof PostgresDatabase)) { - // for now return an empty SQL for DBs that don't support JSON indexes natively. - return new Sql[0]; - } - - StringBuilder builder = new StringBuilder(); - builder.append("CREATE "); - if (statement.isUnique() != null && statement.isUnique()) { - builder.append("UNIQUE "); - } - builder.append("INDEX "); - - if (statement.getIndexName() != null) { - builder.append(database.escapeObjectName(statement.getIndexName(), Index.class)).append(" "); - } - - builder.append("ON ").append(database.escapeTableName(statement.getTableCatalogName(), statement.getTableSchemaName(), - statement.getTableName())); - this.handleJsonIndex(statement, database, builder); - if (StringUtil.trimToNull(statement.getTablespace()) != null && database.supportsTablespaces()) { - builder.append(" TABLESPACE ").append(statement.getTablespace()); - } - - return new Sql[]{new UnparsedSql(builder.toString(), getAffectedIndex(statement))}; - } - - protected void handleJsonIndex(final CreateJsonIndexStatement statement, final Database database, final StringBuilder builder) { - if (database instanceof CockroachDatabase) { - builder.append(" USING gin ("); - builder.append(Arrays.stream(statement.getColumns()).map(AddGeneratedColumnConfig.class::cast) - .map(c -> c.getJsonProperty() == null ? c.getJsonColumn() : - "(" + c.getJsonColumn() + "->'" + c.getJsonProperty() + "')") - .collect(Collectors.joining(", "))) - .append(")"); - } - else if (database instanceof PostgresDatabase) { - builder.append(" USING gin ("); - builder.append(Arrays.stream(statement.getColumns()).map(AddGeneratedColumnConfig.class::cast) - .map(c -> c.getJsonProperty() == null ? c.getJsonColumn() : - "(" + c.getJsonColumn() + "->'" + c.getJsonProperty() + "') jsonb_path_ops") - .collect(Collectors.joining(", "))) - .append(")"); - } - } - - protected Index getAffectedIndex(CreateIndexStatement statement) { - return new Index().setName(statement.getIndexName()).setTable((Table) new Table().setName(statement.getTableName()) - .setSchema(statement.getTableCatalogName(), statement.getTableSchemaName())); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexStatement.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexStatement.java deleted file mode 100644 index 311df28249a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/CreateJsonIndexStatement.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import liquibase.change.AddColumnConfig; -import liquibase.statement.core.CreateIndexStatement; - -/** - * A {@link liquibase.statement.SqlStatement} that holds the information needed to create JSON indexes. Having a specific - * subtype allows for easier selection of the respective {@link liquibase.sqlgenerator.SqlGenerator}, since Liquibase - * selects the generators based on the statement type they are capable of handling. - * - * @author Stefan Guilhen - */ -public class CreateJsonIndexStatement extends CreateIndexStatement { - - public CreateJsonIndexStatement(final String tableCatalogName, final String tableSchemaName, final String tableName, - final String indexName, final Boolean isUnique, final String associatedWith, - final String tablespace, final Boolean clustered, final AddColumnConfig... columns) { - super(indexName, tableCatalogName, tableSchemaName, tableName, isUnique, associatedWith, columns); - super.setTablespace(tablespace); - super.setClustered(clustered); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnChange.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnChange.java deleted file mode 100644 index 48e5c9e1972..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnChange.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import liquibase.change.AbstractChange; -import liquibase.change.AddColumnConfig; -import liquibase.change.Change; -import liquibase.change.ChangeMetaData; -import liquibase.change.ChangeStatus; -import liquibase.change.ChangeWithColumns; -import liquibase.change.ColumnConfig; -import liquibase.change.DatabaseChange; -import liquibase.change.DatabaseChangeProperty; -import liquibase.change.core.AddColumnChange; -import liquibase.database.Database; -import liquibase.database.core.PostgresDatabase; -import liquibase.exception.ValidationErrors; -import liquibase.statement.SqlStatement; -import liquibase.statement.core.AddColumnStatement; - -/** - * Extension used to add generated column to the table. Value is either generated from a property of a JSON file stored in one of the table's - * columns or from hash value of existing column. - *

- * Example configuration in the changelog: - *

- *     <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- *                    xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
- *                    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
- *                    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
- *                    http://www.liquibase.org/xml/ns/dbchangelog-ext
- *                    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
- *
- *     <changeSet author="keycloak" id="some_id">
- *         ...
- *         <ext:addGeneratedColumn tableName="test">
- *             <ext:column name="new_column" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="alias"/>
- *         </ext:addGeneratedColumn>
- *     </changeSet>
- * 
- * The above configuration is adding a new column, named {@code new_column}, whose values are generated from the {@code alias} property - * of the JSON file stored in column {@code metadata}. If, for example, a particular entry in the table contains the JSON - * {@code {"name":"duke","alias":"jduke"}} in column {@code metadata}, the value generated for the new column will be {@code jduke}. - * - *

- * The configuration below adds new generated column named {@code new_column} with value being a hash of a column {@code column}. - * For more information about the type see {@link KeycloakHashDataType}. - *

- * <changeSet author="keycloak" id="some_id">
- *     ...
- *     <ext:addGeneratedColumn tableName="test">
- *         <ext:column name="new_column" type="kc_hash" hashOf="column"/>
- *     </ext:addGeneratedColumn>
- * </changeSet>
- * 
- * @author Stefan Guilhen - */ -@DatabaseChange(name = "addGeneratedColumn", description = "Adds new generated columns to a table.", - priority = ChangeMetaData.PRIORITY_DEFAULT, appliesTo = "table") -public class GeneratedColumnChange extends AbstractChange implements ChangeWithColumns { - - private final ExtendedAddColumnChange delegate; - private Map configMap = new HashMap<>(); - - public GeneratedColumnChange() { - this.delegate = new ExtendedAddColumnChange(); - } - - @DatabaseChangeProperty(mustEqualExisting ="relation.catalog") - public String getCatalogName() { - return this.delegate.getCatalogName(); - } - - public void setCatalogName(final String catalogName) { - this.delegate.setCatalogName(catalogName); - } - - @DatabaseChangeProperty(mustEqualExisting ="relation.schema") - public String getSchemaName() { - return this.delegate.getSchemaName(); - } - - public void setSchemaName(final String schemaName) { - this.delegate.setSchemaName(schemaName); - } - - @DatabaseChangeProperty(mustEqualExisting ="table", description = "Name of the table to add the generated column to") - public String getTableName() { - return this.delegate.getTableName(); - } - - public void setTableName(final String tableName) { - this.delegate.setTableName(tableName); - } - - @Override - public void addColumn(final AddGeneratedColumnConfig column) { - this.delegate.addColumn(column); - this.configMap.put(column.getName(), column); - } - - @Override - @DatabaseChangeProperty(description = "Generated columns information", requiredForDatabase = "all") - public List getColumns() { - return this.delegate.getColumns().stream().map(AddGeneratedColumnConfig.class::cast).collect(Collectors.toList()); - } - - @Override - public void setColumns(final List columns) { - columns.forEach(this.delegate::addColumn); - this.configMap = this.getColumns().stream() - .collect(Collectors.toMap(ColumnConfig::getName, Function.identity())); - } - - @Override - public SqlStatement[] generateStatements(Database database) { - if (database instanceof PostgresDatabase) { - for (AddColumnConfig config : delegate.getColumns()) { - String columnType = config.getType(); - // if postgres, change JSON type to JSONB before generating the statements as JSONB is more efficient. - if (columnType.equalsIgnoreCase("JSON")) { - config.setType("JSONB"); - } - } - } - - // AddColumnChange always produces an AddColumnStatement in the first position of the returned array. - AddColumnStatement delegateStatement = (AddColumnStatement) Arrays.stream(this.delegate.generateStatements(database)) - .findFirst().get(); - - // convert the regular AddColumnStatements into GeneratedColumnStatements, adding the extension properties. - if (!delegateStatement.isMultiple()) { - // single statement - convert it directly. - AddGeneratedColumnConfig config = configMap.get(delegateStatement.getColumnName()); - if (config != null) { - return new SqlStatement[] {new GeneratedColumnStatement(delegateStatement, config.getJsonColumn(), - config.getJsonProperty(), config.getHashOf())}; - } - } - else { - // multiple statement - convert all sub-statements. - List generatedColumnStatements = delegateStatement.getColumns().stream() - .filter(c -> configMap.containsKey(c.getColumnName())) - .map(c -> new GeneratedColumnStatement(c, configMap.get(c.getColumnName()).getJsonColumn(), - configMap.get(c.getColumnName()).getJsonProperty(), configMap.get(c.getColumnName()).getHashOf())) - .collect(Collectors.toList()); - - // add all GeneratedColumnStatements into a composite statement and return the composite. - return new SqlStatement[]{new GeneratedColumnStatement(generatedColumnStatements)}; - } - return new SqlStatement[0]; - } - - @Override - protected Change[] createInverses() { - return this.delegate.createInverses(); - } - - @Override - public ChangeStatus checkStatus(Database database) { - return delegate.checkStatus(database); - } - - @Override - public String getConfirmationMessage() { - return delegate.getConfirmationMessage(); - } - - @Override - public String getSerializedObjectNamespace() { - return GENERIC_CHANGELOG_EXTENSION_NAMESPACE; - } - - @Override - public ValidationErrors validate(Database database) { - ValidationErrors validationErrors = new ValidationErrors(); - validationErrors.checkRequiredField("columns", this.delegate.getColumns()); - // validate each generated column. - this.delegate.getColumns().stream().map(AddGeneratedColumnConfig.class::cast).forEach( - config -> { - if (config.isAutoIncrement() != null && config.isAutoIncrement()) { - validationErrors.addError("Generated column " + config.getName() + " cannot be auto-incremented"); - } else if (config.getValueObject() != null) { - validationErrors.addError("Generated column " + config.getName() + " cannot be configured with a value"); - } else if (config.getDefaultValueObject() != null) { - validationErrors.addError("Generated column " + config.getName() + " cannot be configured with a default value"); - } - if (config.getHashOf() == null && (config.getJsonColumn() == null || config.getJsonProperty() == null)) { - validationErrors.addError("Either 'hashOf' or both 'jsonColumn' and 'jsonProperty' is required"); - } - }); - validationErrors.addAll(super.validate(database)); - return validationErrors; - } - - /** - * Simple extension that makes protected methods public so they can be accessed as a delegate. - */ - private static class ExtendedAddColumnChange extends AddColumnChange { - @Override - public Change[] createInverses() { - return super.createInverses(); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnSqlGenerator.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnSqlGenerator.java deleted file mode 100644 index c246070b46c..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnSqlGenerator.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import java.util.ArrayList; -import java.util.List; - -import liquibase.database.Database; -import liquibase.database.core.PostgresDatabase; -import liquibase.datatype.DataTypeFactory; -import liquibase.datatype.DatabaseDataType; -import liquibase.sql.Sql; -import liquibase.sql.UnparsedSql; -import liquibase.sqlgenerator.SqlGenerator; -import liquibase.sqlgenerator.core.AddColumnGenerator; -import liquibase.statement.core.AddColumnStatement; - -/** - * A {@link SqlGenerator} implementation that supports {@link GeneratedColumnStatement}s. It generates the SQL required - * to add a column whose values are generated from a property of a JSON file stored in one of the table columns - * or a function which is used to create a generated column. - * - * @author Stefan Guilhen - */ -public class GeneratedColumnSqlGenerator extends AddColumnGenerator { - - /** - * Override the priority. This is needed because {@link GeneratedColumnStatement} is a subtype of {@link AddColumnStatement} - * and is thus a match for the standard column generators. By increasing the priority we ensure this is processed before - * the other generators. - * - * @return this generator's priority. - */ - @Override - public int getPriority() { - return SqlGenerator.PRIORITY_DEFAULT + 1; - } - - /** - * Implement {@link #supports(AddColumnStatement, Database)} to return {@code true} only if the statement type is an instance - * of {@link GeneratedColumnStatement}. - *

- * This is needed because this generator is a sub-class of {@link AddColumnGenerator} and is thus registered as being - * able to handle statements of type {@link AddColumnStatement}. Due to the increased priority, this generator ends up - * being selected to handle standard {@code addColumn} changes, which is not desirable. By returning {@code true} only - * when the statement is a {@link GeneratedColumnStatement} we ensure this implementation is selected only when a generated - * column is being added, allowing liquibase to continue iterating through the chain of generators in order to select the - * right generator to handle the standard {@code addColumn} changes. - * - * @param statement the {@link liquibase.statement.SqlStatement} to be processed. - * @param database a reference to the database. - * @return {@code true} if an only if the statement is a {@link GeneratedColumnStatement}; {@code false} otherwise. - */ - @Override - public boolean supports(AddColumnStatement statement, Database database) { - // use this implementation for generated columns only. - return statement instanceof GeneratedColumnStatement; - } - - @Override - protected Sql[] generateSingleColumn(final AddColumnStatement statement, final Database database) { - - StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append(super.generateSingleColumBaseSQL(statement, database)); - sqlBuilder.append(super.generateSingleColumnSQL(statement, database)); - this.handleGeneratedColumn((GeneratedColumnStatement) statement, database, sqlBuilder); - - List returnSql = new ArrayList<>(); - returnSql.add(new UnparsedSql(sqlBuilder.toString(), super.getAffectedColumn(statement))); - - super.addUniqueConstraintStatements(statement, database, returnSql); - super.addForeignKeyStatements(statement, database, returnSql); - - return returnSql.toArray(new Sql[0]); - } - - protected void handleGeneratedColumn(final GeneratedColumnStatement statement, final Database database, final StringBuilder sqlBuilder) { - if (database instanceof PostgresDatabase) { - if (statement.getHashOf() != null) { - sqlBuilder.append(" GENERATED ALWAYS AS (").append(getHashFunction(database, statement.getHashOf())).append(") STORED"); - } else if (statement.getJsonColumn() != null && statement.getJsonProperty() != null) { - // assemble the GENERATED ALWAYS AS section of the query using the json property selection function. - DatabaseDataType columnType = DataTypeFactory.getInstance().fromDescription(statement.getColumnType(), database).toDatabaseDataType(database); - sqlBuilder.append(" GENERATED ALWAYS AS ((").append(statement.getJsonColumn()).append("->>'").append(statement.getJsonProperty()) - .append("')::").append(columnType).append(") stored"); - } - } - } - - private String getHashFunction(Database database, String columnName) { - switch (database.getShortName()) { - case "postgresql": - return String.format("sha256(%s::bytea)", columnName); - case "cockroachdb": - return String.format("sha256(%s)", columnName); - default: - throw new IllegalStateException("Unknown database."); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnStatement.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnStatement.java deleted file mode 100644 index be49f3fe2d9..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/GeneratedColumnStatement.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import java.util.List; - -import liquibase.statement.ColumnConstraint; -import liquibase.statement.core.AddColumnStatement; - -/** - * A {@link liquibase.statement.SqlStatement} that extends the standard {@link AddColumnStatement} to include properties - * to either identify the JSON column and JSON property or a column name ({@code hashOf}) to be used for hashing that are - * to be used to generated the values for the column being added. - * - * @author Stefan Guilhen - */ -public class GeneratedColumnStatement extends AddColumnStatement { - - private String jsonColumn; - private String jsonProperty; - private String hashOf; - - public GeneratedColumnStatement(final AddColumnStatement statement, final String jsonColumn, final String jsonProperty, final String hashOf) { - super(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName(), statement.getColumnName(), - statement.getColumnType(), statement.getDefaultValue(), statement.getRemarks(), - statement.getConstraints().toArray(new ColumnConstraint[0])); - this.jsonColumn = jsonColumn; - this.jsonProperty = jsonProperty; - this.hashOf = hashOf; - } - - public GeneratedColumnStatement(final List statements) { - super(statements.toArray(new GeneratedColumnStatement[0])); - } - - /** - * Obtains the name of the column that holds JSON files. - * - * @return the name of the JSON column. - */ - public String getJsonColumn() { - return this.jsonColumn; - } - - /** - * Obtains the name of the property in the JSON file whose value is to be used as the generated value for the new column. - * - * @return the name of the JSON property. - */ - public String getJsonProperty() { - return this.jsonProperty; - } - - /** - * Obtains the column name (hashOf) to be used as the value for hashing for the new column. - */ - public String getHashOf() { - return hashOf; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/JsonDataType.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/JsonDataType.java deleted file mode 100644 index 58b7c446dbb..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/JsonDataType.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.storage.jpa.liquibase.extension; - -import liquibase.change.core.LoadDataChange; -import liquibase.database.Database; -import liquibase.database.core.PostgresDatabase; -import liquibase.datatype.DataTypeInfo; -import liquibase.datatype.DatabaseDataType; -import liquibase.datatype.LiquibaseDataType; - -/** - * A {@link LiquibaseDataType} to handle the JSON column type. - * - * @author Stefan Guilhen - */ -@DataTypeInfo(name="json", minParameters = 0, maxParameters = 1, priority = LiquibaseDataType.PRIORITY_DEFAULT + 1) -public class JsonDataType extends LiquibaseDataType { - - @Override - public DatabaseDataType toDatabaseDataType(Database database) { - if (database instanceof PostgresDatabase) { - // on Postgres switch the columns of type JSON to JSONB as JSONB is a more efficient type to handle JSON contents. - return new DatabaseDataType("JSONB", super.getParameters()); - } - return super.toDatabaseDataType(database); - } - - @Override - public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() { - return LoadDataChange.LOAD_DATA_TYPE.UNKNOWN; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/KeycloakHashDataType.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/KeycloakHashDataType.java deleted file mode 100644 index 98c92c56b23..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/KeycloakHashDataType.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.liquibase.extension; - -import liquibase.change.core.LoadDataChange; -import liquibase.database.Database; -import liquibase.database.core.CockroachDatabase; -import liquibase.datatype.DataTypeInfo; -import liquibase.datatype.DatabaseDataType; -import liquibase.datatype.LiquibaseDataType; - -/** - * A {@link LiquibaseDataType} to handle hashed value of other column. - */ -@DataTypeInfo(name="kc_hash", minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT) -public class KeycloakHashDataType extends LiquibaseDataType { - - @Override - public DatabaseDataType toDatabaseDataType(Database database) { - if (database instanceof CockroachDatabase) { - // currently cockroachdb returns string - // there is open issue to make it compatible with postgres: https://github.com/cockroachdb/cockroach/issues/73896 - return new DatabaseDataType("STRING", super.getParameters()); - } - return new DatabaseDataType("BYTEA", super.getParameters()); - } - - @Override - public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() { - return LoadDataChange.LOAD_DATA_TYPE.BLOB; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/KeycloakKeyDataType.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/KeycloakKeyDataType.java deleted file mode 100644 index 8d809b573fb..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/extension/KeycloakKeyDataType.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.liquibase.extension; - -import liquibase.change.core.LoadDataChange; -import liquibase.database.Database; -import liquibase.datatype.DataTypeInfo; -import liquibase.datatype.DatabaseDataType; -import liquibase.datatype.LiquibaseDataType; -import liquibase.datatype.core.VarcharType; - -/** - * A {@link LiquibaseDataType} used in columns that reference an entity that can be external to the JPA storage. In other - * words, they reference objects that might be stored in different storages, with different id formats than those used - * by the JPA storage. - *

- * Its usage is encouraged to bring consistency to all columns that might reference external entities. - * - * @author Stefan Guilhen - */ -@DataTypeInfo(name="kc_key", minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT) -public class KeycloakKeyDataType extends LiquibaseDataType { - - @Override - public DatabaseDataType toDatabaseDataType(Database database) { - // convert the type into a varchar of size 4000. - VarcharType varcharType = new VarcharType(); - varcharType.addParameter("4000"); - return varcharType.toDatabaseDataType(database); - } - - @Override - public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() { - return LoadDataChange.LOAD_DATA_TYPE.STRING; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/lockservice/KeycloakLockService.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/lockservice/KeycloakLockService.java deleted file mode 100644 index 14178dbe1c7..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/lockservice/KeycloakLockService.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.liquibase.lockservice; - -import liquibase.exception.DatabaseException; -import liquibase.lockservice.StandardLockService; -import liquibase.snapshot.DatabaseSnapshot; -import liquibase.snapshot.InvalidExampleException; -import liquibase.snapshot.SnapshotControl; -import liquibase.snapshot.SnapshotGeneratorFactory; -import liquibase.structure.core.PrimaryKey; -import liquibase.structure.core.Schema; -import liquibase.structure.core.Table; -import org.jboss.logging.Logger; - -/** - * Extending the Liquibase {@link StandardLockService} for situations where it failed on a H2 database. - * - * @author Alexander Schwartz - */ -public class KeycloakLockService extends StandardLockService { - - private static final Logger log = Logger.getLogger(KeycloakLockService.class); - - @Override - public int getPriority() { - return super.getPriority() + 1; - } - - @Override - protected boolean hasDatabaseChangeLogLockTable() throws DatabaseException { - boolean originalReturnValue = super.hasDatabaseChangeLogLockTable(); - if (originalReturnValue) { - /* Liquibase only checks that the table exists. On the H2 database, creation of a table with a primary key is not atomic, - and the primary key might not be visible yet. The primary key would be needed to prevent inserting the data into the table - a second time. Inserting it a second time might lead to a failure when creating the primary key, which would then roll back - the creation of the table. Therefore, at least on the H2 database, checking for the primary key is essential. - - An existing DATABASECHANGELOG might indicate that the insertion of data was completed previously. - Still, this isn't working with the DBLockTest which deletes only the DATABASECHANGELOGLOCK table. - - See https://github.com/keycloak/keycloak/issues/15487 for more information. - */ - Table lockTable = (Table) new Table().setName(database.getDatabaseChangeLogLockTableName()).setSchema( - new Schema(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName())); - SnapshotGeneratorFactory instance = SnapshotGeneratorFactory.getInstance(); - - try { - DatabaseSnapshot snapshot = instance.createSnapshot(lockTable.getSchema().toCatalogAndSchema(), database, - new SnapshotControl(database, false, Table.class, PrimaryKey.class).setWarnIfObjectNotFound(false)); - Table lockTableFromSnapshot = snapshot.get(lockTable); - if (lockTableFromSnapshot == null) { - throw new RuntimeException("DATABASECHANGELOGLOCK not found, although Liquibase claims it exists."); - } else if (lockTableFromSnapshot.getPrimaryKey() == null) { - log.warn("Primary key not found - table creation not complete yet."); - return false; - } - } catch (InvalidExampleException e) { - throw new RuntimeException(e); - } - } - return originalReturnValue; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/updater/MapJpaLiquibaseUpdaterProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/updater/MapJpaLiquibaseUpdaterProvider.java deleted file mode 100644 index 70484ca5dda..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/updater/MapJpaLiquibaseUpdaterProvider.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.storage.jpa.liquibase.updater; - -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.database.core.CockroachDatabase; -import org.keycloak.models.map.storage.jpa.liquibase.connection.JdbcConnectionFromPool; -import org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionProvider; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.List; -import liquibase.Contexts; -import liquibase.LabelExpression; -import liquibase.Liquibase; -import liquibase.changelog.ChangeSet; -import liquibase.changelog.RanChangeSet; -import liquibase.exception.LiquibaseException; -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider; - -public class MapJpaLiquibaseUpdaterProvider implements MapJpaUpdaterProvider { - - private static final Logger logger = Logger.getLogger(MapJpaLiquibaseUpdaterProvider.class); - - private final KeycloakSession session; - private String databaseShortName; - - public MapJpaLiquibaseUpdaterProvider(KeycloakSession session) { - this.session = session; - } - - @Override - public void update(Class modelType, Connection connection, String defaultSchema) { - // Liquibase has a global Scopes / a global ScopeManager by default, and SqlGeneratorFactory is always global - // therefore, ensure that only one migration runs at a time - synchronized (MapJpaLiquibaseUpdaterProvider.class) { - this.updateSynch(modelType, connection, null, defaultSchema); - } - } - - @Override - public void export(Class modelType, Connection connection, String defaultSchema, File file) { - // Liquibase has a global Scopes / a global ScopeManager by default, and SqlGeneratorFactory is always global - // therefore, ensure that only one migration runs at a time - synchronized (MapJpaLiquibaseUpdaterProvider.class) { - this.updateSynch(modelType, connection, file, defaultSchema); - } - } - - protected void updateSynch(Class modelType, Connection connection, File file, String defaultSchema) { - logger.debug("Starting database update"); - - Writer exportWriter = null; - try (Liquibase liquibase = getLiquibase(modelType, connection, defaultSchema)) { - - if (file != null) { - exportWriter = new FileWriter(file); - } - - updateChangeSet(liquibase); - - } catch (LiquibaseException | IOException | SQLException e) { - logger.error("Error has occurred while updating the database", e); - throw new RuntimeException("Failed to update database", e); - } finally { - if (exportWriter != null) { - try { - exportWriter.close(); - } catch (IOException ioe) { - // ignore - } - } - } - } - - protected void updateChangeSet(Liquibase liquibase) throws LiquibaseException, SQLException { - String changelog = liquibase.getChangeLogFile(); - List changeSets = this.getLiquibaseUnrunChangeSets(liquibase); - if (!changeSets.isEmpty()) { - List ranChangeSets = liquibase.getDatabase().getRanChangeSetList(); - if (ranChangeSets.isEmpty()) { - logger.infov("Initializing database schema. Using changelog {0}", changelog); - } else { - if (logger.isDebugEnabled()) { - logger.debugv("Updating database from {0} to {1}. Using changelog {2}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog); - } else { - logger.infov("Updating database. Using changelog {0}", changelog); - } - } - - liquibase.update((Contexts) null); - - logger.debugv("Completed database update for changelog {0}", changelog); - } else { - logger.debugv("Database is up to date for changelog {0}", changelog); - } - - } - - @Override - public Status validate(Class modelType, Connection connection, String defaultSchema) { - // Liquibase has a global Scopes / a global ScopeManager by default - // therefore, ensure that only one Scope of liquibase runs at a time - synchronized (MapJpaLiquibaseUpdaterProvider.class) { - return this.validateSynch(modelType, connection, defaultSchema); - } - } - - protected Status validateSynch(final Class modelType, final Connection connection, final String defaultSchema) { - logger.debug("Validating if database is updated"); - - try (Liquibase liquibase = getLiquibase(modelType, connection, defaultSchema)) { - - Status status = validateChangeSet(liquibase, liquibase.getChangeLogFile()); - if (status != Status.VALID) { - return status; - } - - } catch (LiquibaseException e) { - throw new RuntimeException("Failed to validate database", e); - } - - return Status.VALID; - } - - protected Status validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException { - final Status result; - List changeSets = this.getLiquibaseUnrunChangeSets(liquibase); - - if (!changeSets.isEmpty()) { - if (changeSets.size() == liquibase.getDatabaseChangeLog().getChangeSets().size()) { - result = Status.EMPTY; - } else { - logger.debugf("Validation failed. Database is not up-to-date for changelog %s", changelog); - result = Status.OUTDATED; - } - } else { - logger.debugf("Validation passed. Database is up-to-date for changelog %s", changelog); - result = Status.VALID; - } - - return result; - } - - private List getLiquibaseUnrunChangeSets(Liquibase liquibase) throws LiquibaseException { - return liquibase.listUnrunChangeSets(null, new LabelExpression(), false); - } - - private Liquibase getLiquibase(Class modelType, Connection connection, String defaultSchema) throws LiquibaseException { - MapLiquibaseConnectionProvider liquibaseProvider = session.getProvider(MapLiquibaseConnectionProvider.class); - String modelName = ModelEntityUtil.getModelName(modelType); - if (modelName == null) { - throw new IllegalStateException("Cannot find changlelog for modelClass " + modelType.getName()); - } - - // for authorization services there is used single name for all modelTypes - modelName = modelName.startsWith("authz-") ? "authz" : modelName; - - // for events, map both event types to a single changelog name - if (modelName.equals("auth-events") || modelName.equals("admin-events")) - modelName = "events"; - - // This acts on the unwrapped database connection as Liquibase will commit and rollback the transaction as needed. - // Otherwise, the connection will not recover from an SQL error when running for example on a PostgreSQL database. - // This was needed when adding support for JTA - try (Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnectionFromPool(connection.unwrap(Connection.class)))) { - // if the database is cockroachdb, use the aggregate changelog (see GHI #11230). - String changelog = database instanceof CockroachDatabase ? "META-INF/jpa-aggregate-changelog.xml" : "META-INF/jpa-" + modelName + "-changelog.xml"; - databaseShortName = database.getShortName(); - return liquibaseProvider.getLiquibaseForCustomUpdate(connection, defaultSchema, changelog, this.getClass().getClassLoader(), "databasechangelog"); - } catch (SQLException e) { - throw new LiquibaseException(e); - } - } - - @Override - public void close() { - } - - @Override - public String getDatabaseShortName() { - return databaseShortName; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/updater/MapJpaLiquibaseUpdaterProviderFactory.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/updater/MapJpaLiquibaseUpdaterProviderFactory.java deleted file mode 100644 index 5154275b93d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/liquibase/updater/MapJpaLiquibaseUpdaterProviderFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.storage.jpa.liquibase.updater; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProviderFactory; - -public class MapJpaLiquibaseUpdaterProviderFactory implements MapJpaUpdaterProviderFactory { - - public static final String PROVIDER_ID = "map-liquibase-updater"; - - @Override - public MapJpaUpdaterProvider create(KeycloakSession session) { - return new MapJpaLiquibaseUpdaterProvider(session); - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - } - - @Override - public String getId() { - return PROVIDER_ID; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/JpaLockMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/JpaLockMapStorage.java deleted file mode 100644 index bf2bb095b0f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/JpaLockMapStorage.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023 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 Locks and - * limitations under the License. - */ -package org.keycloak.models.map.storage.jpa.lock; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.lock.MapLockEntity; -import org.keycloak.models.map.lock.MapLockEntityDelegate; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.lock.delegate.JpaLockDelegateProvider; -import org.keycloak.models.map.storage.jpa.lock.entity.JpaLockEntity; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -public class JpaLockMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaLockMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaLockEntity.class, MapLockEntity.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaLockEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("name")); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_LOCK); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaLockModelCriteriaBuilder(); - } - - @Override - protected MapLockEntity mapToEntityDelegate(JpaLockEntity original) { - return new MapLockEntityDelegate(new JpaLockDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/JpaLockModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/JpaLockModelCriteriaBuilder.java deleted file mode 100644 index e1bb0cd0df1..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/JpaLockModelCriteriaBuilder.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.lock; - -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.lock.MapLockEntity; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity; -import org.keycloak.storage.SearchableModelField; - -import java.util.Objects; -import java.util.UUID; - -import static org.keycloak.models.map.lock.MapLockEntity.SearchableFields; - -public class JpaLockModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaLockModelCriteriaBuilder() { - super(JpaLockModelCriteriaBuilder::new); - } - - private JpaLockModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaLockModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaLockModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.NAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaLockModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/delegate/JpaLockDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/delegate/JpaLockDelegateProvider.java deleted file mode 100644 index 682114437bf..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/delegate/JpaLockDelegateProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.lock.delegate; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.lock.MapLockEntity; -import org.keycloak.models.map.lock.MapLockEntityFields; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.lock.entity.JpaLockEntity; - -import jakarta.persistence.EntityManager; -import java.util.UUID; - -/** - * A {@link DelegateProvider} implementation for {@link JpaLockEntity}. - * - * @author Stefan Guilhen - */ -public class JpaLockDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaLockDelegateProvider(final JpaLockEntity delegate, final EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapLockEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapLockEntityFields) { - switch ((MapLockEntityFields) field) { - case ID: - case NAME: - return getDelegate(); - - default: - setDelegate(em.find(JpaLockEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid lock field: " + field); - } - } else { - setDelegate(em.find(JpaLockEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/entity/JpaLockEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/entity/JpaLockEntity.java deleted file mode 100644 index f4fc5dd3c6f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/entity/JpaLockEntity.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.lock.entity; - -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - -import static org.keycloak.models.map.lock.MapLockEntity.AbstractLockEntity; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP; - - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_lock", uniqueConstraints = {@UniqueConstraint(columnNames = {"name"})}) -public class JpaLockEntity extends AbstractLockEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaLockMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaLockEntity() { - this.metadata = new JpaLockMetadata(); - } - - public JpaLockEntity(DeepCloner cloner) { - this.metadata = new JpaLockMetadata(cloner); - } - - public JpaLockEntity(final UUID id, final int version, final Integer entityVersion, String name) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.name = name; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_GROUP; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public String getKeycloakInstanceIdentifier() { - return metadata.getKeycloakInstanceIdentifier(); - } - - @Override - public void setKeycloakInstanceIdentifier(String keycloakInstanceIdentifier) { - metadata.setKeycloakInstanceIdentifier(keycloakInstanceIdentifier); - } - - @Override - public Long getTimeAcquired() { - return metadata.getTimeAcquired(); - } - - @Override - public void setTimeAcquired(Long timeAcquired) { - metadata.setTimeAcquired(timeAcquired); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaLockEntity)) return false; - return Objects.equals(getId(), ((JpaLockEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/entity/JpaLockMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/entity/JpaLockMetadata.java deleted file mode 100644 index e6568c3a90c..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/lock/entity/JpaLockMetadata.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 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.storage.jpa.lock.entity; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.lock.MapLockEntityImpl; - -import java.io.Serializable; - -public class JpaLockMetadata extends MapLockEntityImpl implements Serializable { - - public JpaLockMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaLockMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapStorage.java deleted file mode 100644 index f56f11d287d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapStorage.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.loginFailure; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityDelegate; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.loginFailure.delegate.JpaUserLoginFailureDelegateProvider; -import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE; - -/** - * A {@link JpaMapStorage} implementation for user login failure entities. - * - * @author Stefan Guilhen - */ -public class JpaUserLoginFailureMapStorage extends JpaMapStorage { - - @SuppressWarnings("unchecked") - public JpaUserLoginFailureMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaUserLoginFailureEntity.class, UserLoginFailureModel.class, em); - } - - @Override - public Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaUserLoginFailureEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("userId") - ); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaUserLoginFailureModelCriteriaBuilder(); - } - - @Override - protected MapUserLoginFailureEntity mapToEntityDelegate(JpaUserLoginFailureEntity original) { - return new MapUserLoginFailureEntityDelegate(new JpaUserLoginFailureDelegateProvider(original, em)); - } -} \ No newline at end of file diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java deleted file mode 100644 index 40c69e1ae21..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.loginFailure; - -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity; -import org.keycloak.storage.SearchableModelField; - -/** - * A {@link JpaModelCriteriaBuilder} implementation for user login failures. - * - * @author Stefan Guilhen - */ -public class JpaUserLoginFailureModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaUserLoginFailureModelCriteriaBuilder() { - super(JpaUserLoginFailureModelCriteriaBuilder::new); - } - - private JpaUserLoginFailureModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaUserLoginFailureModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaUserLoginFailureModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == UserLoginFailureModel.SearchableFields.REALM_ID || - modelField == UserLoginFailureModel.SearchableFields.USER_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserLoginFailureModelCriteriaBuilder((cb, subQueryProvider, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/delegate/JpaUserLoginFailureDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/delegate/JpaUserLoginFailureDelegateProvider.java deleted file mode 100644 index 1c765de5dd5..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/delegate/JpaUserLoginFailureDelegateProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.loginFailure.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityFields; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity; - -/** - * A {@link DelegateProvider} implementation for {@link JpaUserLoginFailureEntity}. - * - * @author Stefan Guilhen - */ -public class JpaUserLoginFailureDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaUserLoginFailureDelegateProvider(JpaUserLoginFailureEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapUserLoginFailureEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapUserLoginFailureEntityFields) { - switch ((MapUserLoginFailureEntityFields) field) { - case ID: - case REALM_ID: - case USER_ID: - return getDelegate(); - default: - setDelegate(em.find(JpaUserLoginFailureEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid user login failure field: " + field); - } - } else { - setDelegate(em.find(JpaUserLoginFailureEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/entity/JpaUserLoginFailureEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/entity/JpaUserLoginFailureEntity.java deleted file mode 100644 index 13c40f6f8ad..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/entity/JpaUserLoginFailureEntity.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.loginFailure.entity; - -import java.util.Objects; -import java.util.UUID; - -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Parameter; -import org.hibernate.annotations.Type; -import org.hibernate.usertype.UserTypeLegacyBridge; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE; - -/** - * JPA {@link MapUserLoginFailureEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_user_login_failure", - uniqueConstraints = { - @UniqueConstraint( - columnNames = {"realmId", "userId"} - ) -}) -public class JpaUserLoginFailureEntity extends MapUserLoginFailureEntity.AbstractUserLoginFailureEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaUserLoginFailureMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String userId; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaUserLoginFailureEntity() { - this.metadata = new JpaUserLoginFailureMetadata(); - } - - public JpaUserLoginFailureEntity(DeepCloner cloner) { - this.metadata = new JpaUserLoginFailureMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select user login failure without metadata(json) field. - */ - public JpaUserLoginFailureEntity(UUID id, int version, Integer entityVersion, String realmId, String userId) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.userId = userId; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return this.metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return this.entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - this.metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE; - } - - @Override - public int getVersion() { - return this.version; - } - - @Override - public String getId() { - return this.id == null ? null : this.id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return this.metadata.getRealmId(); - return this.realmId; - } - - @Override - public void setRealmId(String realmId) { - this.metadata.setRealmId(realmId); - } - - @Override - public String getUserId() { - if (isMetadataInitialized()) return this.metadata.getUserId(); - return this.userId; - } - - @Override - public void setUserId(String userId) { - this.metadata.setUserId(userId); - } - - @Override - public Long getFailedLoginNotBefore() { - return this.metadata.getFailedLoginNotBefore(); - } - - @Override - public void setFailedLoginNotBefore(Long failedLoginNotBefore) { - this.metadata.setFailedLoginNotBefore(failedLoginNotBefore); - } - - @Override - public Integer getNumFailures() { - return this.metadata.getNumFailures(); - } - - @Override - public void setNumFailures(Integer numFailures) { - this.metadata.setNumFailures(numFailures); - } - - @Override - public Long getLastFailure() { - return this.metadata.getLastFailure(); - } - - @Override - public void setLastFailure(Long lastFailure) { - this.metadata.setLastFailure(lastFailure); - } - - @Override - public String getLastIPFailure() { - return this.metadata.getLastIPFailure(); - } - - @Override - public void setLastIPFailure(String lastIPFailure) { - this.metadata.setLastIPFailure(lastIPFailure); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaUserLoginFailureEntity)) return false; - return Objects.equals(getId(), ((JpaUserLoginFailureEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/entity/JpaUserLoginFailureMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/entity/JpaUserLoginFailureMetadata.java deleted file mode 100644 index f540c5899ee..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/entity/JpaUserLoginFailureMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.loginFailure.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityImpl; - -/** - * Class that contains all the user login failure metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaUserLoginFailureMetadata extends MapUserLoginFailureEntityImpl implements Serializable { - - public JpaUserLoginFailureMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaUserLoginFailureMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapStorage.java deleted file mode 100644 index efdc4be3ec3..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapStorage.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.MapRealmEntityDelegate; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.realm.delegate.JpaRealmDelegateProvider; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM; - -/** - * A {@link MapStorage} implementation for realm entities. - * - * @author Stefan Guilhen - */ -public class JpaRealmMapStorage extends JpaMapStorage { - - public JpaRealmMapStorage(KeycloakSession session, final EntityManager em) { - super(session, JpaRealmEntity.class, RealmModel.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaRealmEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("name"), - root.get("displayName"), - root.get("displayNameHtml"), - root.get("enabled") - ); - } - - @Override - protected void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_REALM); - } - - @Override - protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaRealmModelCriteriaBuilder(); - } - - @Override - protected MapRealmEntity mapToEntityDelegate(JpaRealmEntity original) { - return new MapRealmEntityDelegate(new JpaRealmDelegateProvider(original, em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java deleted file mode 100644 index bc99fe42dc7..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm; - -import jakarta.persistence.criteria.JoinType; - -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity; -import org.keycloak.storage.SearchableModelField; - - -/** - * A {@link JpaModelCriteriaBuilder} implementation for realms. - * - * @author Stefan Guilhen - */ -public class JpaRealmModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaRealmModelCriteriaBuilder() { - super(JpaRealmModelCriteriaBuilder::new); - } - - private JpaRealmModelCriteriaBuilder(final JpaPredicateFunction predicateFunc) { - super(JpaRealmModelCriteriaBuilder::new, predicateFunc); - } - - private JpaRealmModelCriteriaBuilder(final JpaPredicateFunction predicateFunc, - final boolean isDistinct) { - super(JpaRealmModelCriteriaBuilder::new, predicateFunc, isDistinct); - } - - @Override - public JpaRealmModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch(op) { - case EQ: - if (modelField == RealmModel.SearchableFields.NAME) { - validateValue(value, modelField, op, String.class); - return new JpaRealmModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE) { - validateValue(value, modelField, op, String.class); - return new JpaRealmModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.join("components", JoinType.LEFT).get("providerType"), value[0]), true); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case EXISTS: - if (modelField == RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS) { - return new JpaRealmModelCriteriaBuilder((cb, query, root) -> - cb.isTrue(cb.function("->", JsonbType.class, root.get("metadata"), - cb.literal("fClientInitialAccesses")).isNotNull()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/delegate/JpaRealmDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/delegate/JpaRealmDelegateProvider.java deleted file mode 100644 index 9ffee349639..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/delegate/JpaRealmDelegateProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.MapRealmEntityFields; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity; - -/** - * A {@link DelegateProvider} implementation for {@link JpaRealmEntity}. - * - * @author Stefan Guilhen - */ -public class JpaRealmDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaRealmDelegateProvider(final JpaRealmEntity delegate, final EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapRealmEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapRealmEntityFields) { - switch ((MapRealmEntityFields) field) { - case ID: - case NAME: - case DISPLAY_NAME: - case DISPLAY_NAME_HTML: - case ENABLED: - return getDelegate(); - - case ATTRIBUTES: - this.setDelegateWithAssociation("attributes"); - break; - - case COMPONENTS: - this.setDelegateWithAssociation("components"); - break; - - default: - setDelegate(em.find(JpaRealmEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid realm field: " + field); - } - } else { - setDelegate(em.find(JpaRealmEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - - protected void setDelegateWithAssociation(final String associationName) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaRealmEntity.class); - Root root = query.from(JpaRealmEntity.class); - root.fetch(associationName, JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - setDelegate(em.createQuery(query).getSingleResult()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaComponentEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaComponentEntity.java deleted file mode 100644 index bf762383104..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaComponentEntity.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm.entity; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.realm.entity.MapComponentEntity; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - - -/** - * JPA {@link MapComponentEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - *

- * Components are independent (i.e. a component doesn't depend on another component) and can be manipulated directly via - * the component endpoints. - *

- * By implementing {@link JpaRootVersionedEntity}, this entity will enforce optimistic locking, which can lead to - * {@link jakarta.persistence.OptimisticLockException} if more than one thread attempts to modify the same component - * at the same time. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_component") -public class JpaComponentEntity extends UpdatableEntity.Impl implements MapComponentEntity, JpaRootVersionedEntity, JpaChildEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String providerType; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaComponentMetadata metadata; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="fk_root") - private JpaRealmEntity root; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaComponentEntity() { - this.metadata = new JpaComponentMetadata(); - } - - public JpaComponentEntity(DeepCloner cloner) { - this.metadata = new JpaComponentMetadata(cloner); - } - - @Override - public JpaRealmEntity getParent() { - return root; - } - - public void setParent(JpaRealmEntity root) { - this.root = root; - } - - public boolean isMetadataInitialized() { - return this.metadata != null; - } - - @Override - public String getId() { - return this.id == null ? null : this.id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return this.metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer version) { - this.metadata.setEntityVersion(version); - } - - @Override - public int getVersion() { - return version; - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_COMPONENT; - } - - @Override - public String getName() { - return this.metadata.getName(); - } - - @Override - public void setName(String name) { - this.metadata.setName(name); - } - - @Override - public String getProviderId() { - return this.metadata.getProviderId(); - } - - @Override - public void setProviderId(String providerId) { - this.metadata.setProviderId(providerId); - } - - @Override - public String getProviderType() { - if (this.isMetadataInitialized()) return this.metadata.getProviderType(); - return this.providerType; - } - - @Override - public void setProviderType(String providerType) { - this.metadata.setProviderType(providerType); - } - - @Override - public String getSubType() { - return this.metadata.getSubType(); - } - - @Override - public void setSubType(String subType) { - this.metadata.setSubType(subType); - } - - @Override - public String getParentId() { - return this.metadata.getParentId(); - } - - @Override - public void setParentId(String parentId) { - this.metadata.setParentId(parentId); - } - - @Override - public Map> getConfig() { - return this.metadata.getConfig(); - } - - @Override - public void setConfig(Map> config) { - this.metadata.setConfig(config); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaComponentEntity)) return false; - return Objects.equals(getId(), ((JpaComponentEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaComponentMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaComponentMetadata.java deleted file mode 100644 index 62ef0ca414f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaComponentMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.realm.entity.MapComponentEntityImpl; - -/** - * Class that contains all the component metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaComponentMetadata extends MapComponentEntityImpl implements Serializable { - - public JpaComponentMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaComponentMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmAttributeEntity.java deleted file mode 100644 index a1b9fed7e03..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmAttributeEntity.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -/** - * JPA implementation for realm attributes. This entity represents a realm attribute and has a many-to-one relationship - * with the realm entity. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_realm_attribute", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaRealmAttributeEntity extends JpaAttributeEntityWithHashValue { - - public JpaRealmAttributeEntity() { - } - - public JpaRealmAttributeEntity(final JpaRealmEntity root, final String name, final String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmEntity.java deleted file mode 100644 index 8e858f951fb..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmEntity.java +++ /dev/null @@ -1,1085 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm.entity; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapComponentEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER; - -/** - * JPA {@link MapRealmEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_realm", - uniqueConstraints = { - @UniqueConstraint( - columnNames = {"name"} - ) -}) -@SuppressWarnings("ConstantConditions") -public class JpaRealmEntity extends MapRealmEntity.AbstractRealmEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaRealmMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String displayName; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String displayNameHtml; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Boolean enabled; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set components = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaRealmEntity() { - this.metadata = new JpaRealmMetadata(); - } - - public JpaRealmEntity(final DeepCloner cloner) { - this.metadata = new JpaRealmMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select realm without metadata(json) field. - */ - public JpaRealmEntity(final UUID id, final int version, final Integer entityVersion, final String name, - final String displayName, final String displayNameHtml, final Boolean enabled) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.name = name; - this.displayName = displayName; - this.displayNameHtml = displayNameHtml; - this.enabled = enabled; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_REALM; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getName() { - if (isMetadataInitialized()) return this.metadata.getName(); - return this.name; - } - - @Override - public void setName(String name) { - this.metadata.setName(name); - } - - @Override - public String getDisplayName() { - if (isMetadataInitialized()) return this.metadata.getDisplayName(); - return this.displayName; - } - - @Override - public void setDisplayName(String displayName) { - this.metadata.setDisplayName(displayName); - } - - @Override - public String getDisplayNameHtml() { - if (isMetadataInitialized()) return this.metadata.getDisplayNameHtml(); - return this.displayNameHtml; - } - - @Override - public void setDisplayNameHtml(String displayNameHtml) { - this.metadata.setDisplayNameHtml(displayNameHtml); - } - - @Override - public Boolean isEnabled() { - if (isMetadataInitialized()) return this.metadata.isEnabled(); - return this.enabled; - } - - @Override - public void setEnabled(Boolean enabled) { - this.metadata.setEnabled(enabled); - } - - @Override - public Boolean isRegistrationAllowed() { - return this.metadata.isRegistrationAllowed(); - } - - @Override - public void setRegistrationAllowed(Boolean registrationAllowed) { - this.metadata.setRegistrationAllowed(registrationAllowed); - } - - @Override - public Boolean isRegistrationEmailAsUsername() { - return this.metadata.isRegistrationEmailAsUsername(); - } - - @Override - public void setRegistrationEmailAsUsername(Boolean registrationEmailAsUsername) { - this.metadata.setRegistrationEmailAsUsername(registrationEmailAsUsername); - } - - @Override - public Boolean isVerifyEmail() { - return this.metadata.isVerifyEmail(); - } - - @Override - public void setVerifyEmail(Boolean verifyEmail) { - this.metadata.setVerifyEmail(verifyEmail); - } - - @Override - public Boolean isResetPasswordAllowed() { - return this.metadata.isResetPasswordAllowed(); - } - - @Override - public void setResetPasswordAllowed(Boolean resetPasswordAllowed) { - this.metadata.setResetPasswordAllowed(resetPasswordAllowed); - } - - @Override - public Boolean isLoginWithEmailAllowed() { - return this.metadata.isLoginWithEmailAllowed(); - } - - @Override - public void setLoginWithEmailAllowed(Boolean loginWithEmailAllowed) { - this.metadata.setLoginWithEmailAllowed(loginWithEmailAllowed); - } - - @Override - public Boolean isDuplicateEmailsAllowed() { - return this.metadata.isDuplicateEmailsAllowed(); - } - - @Override - public void setDuplicateEmailsAllowed(Boolean duplicateEmailsAllowed) { - this.metadata.setDuplicateEmailsAllowed(duplicateEmailsAllowed); - } - - @Override - public Boolean isRememberMe() { - return this.metadata.isRememberMe(); - } - - @Override - public void setRememberMe(Boolean rememberMe) { - this.metadata.setRememberMe(rememberMe); - } - - @Override - public Boolean isEditUsernameAllowed() { - return this.metadata.isEditUsernameAllowed(); - } - - @Override - public void setEditUsernameAllowed(Boolean editUsernameAllowed) { - this.metadata.setEditUsernameAllowed(editUsernameAllowed); - } - - @Override - public Boolean isRevokeRefreshToken() { - return this.metadata.isRevokeRefreshToken(); - } - - @Override - public void setRevokeRefreshToken(Boolean revokeRefreshToken) { - this.metadata.setRevokeRefreshToken(revokeRefreshToken); - } - - @Override - public Boolean isAdminEventsEnabled() { - return this.metadata.isAdminEventsEnabled(); - } - - @Override - public void setAdminEventsEnabled(Boolean adminEventsEnabled) { - this.metadata.setAdminEventsEnabled(adminEventsEnabled); - } - - @Override - public Boolean isAdminEventsDetailsEnabled() { - return this.metadata.isAdminEventsDetailsEnabled(); - } - - @Override - public void setAdminEventsDetailsEnabled(Boolean adminEventsDetailsEnabled) { - this.metadata.setAdminEventsDetailsEnabled(adminEventsDetailsEnabled); - } - - @Override - public Boolean isInternationalizationEnabled() { - return this.metadata.isInternationalizationEnabled(); - } - - @Override - public void setInternationalizationEnabled(Boolean internationalizationEnabled) { - this.metadata.setInternationalizationEnabled(internationalizationEnabled); - } - - @Override - public Boolean isAllowUserManagedAccess() { - return this.metadata.isAllowUserManagedAccess(); - } - - @Override - public void setAllowUserManagedAccess(Boolean allowUserManagedAccess) { - this.metadata.setAllowUserManagedAccess(allowUserManagedAccess); - } - - @Override - public Boolean isOfflineSessionMaxLifespanEnabled() { - return this.metadata.isOfflineSessionMaxLifespanEnabled(); - } - - @Override - public void setOfflineSessionMaxLifespanEnabled(Boolean offlineSessionMaxLifespanEnabled) { - this.metadata.setOfflineSessionMaxLifespanEnabled(offlineSessionMaxLifespanEnabled); - } - - @Override - public Boolean isEventsEnabled() { - return this.metadata.isEventsEnabled(); - } - - @Override - public void setEventsEnabled(Boolean eventsEnabled) { - this.metadata.setEventsEnabled(eventsEnabled); - } - - @Override - public Integer getRefreshTokenMaxReuse() { - return this.metadata.getRefreshTokenMaxReuse(); - } - - @Override - public void setRefreshTokenMaxReuse(Integer refreshTokenMaxReuse) { - this.metadata.setRefreshTokenMaxReuse(refreshTokenMaxReuse); - } - - @Override - public Integer getSsoSessionIdleTimeout() { - return this.metadata.getSsoSessionIdleTimeout(); - } - - @Override - public void setSsoSessionIdleTimeout(Integer ssoSessionIdleTimeout) { - this.metadata.setSsoSessionIdleTimeout(ssoSessionIdleTimeout); - } - - @Override - public Integer getSsoSessionMaxLifespan() { - return this.metadata.getSsoSessionMaxLifespan(); - } - - @Override - public void setSsoSessionMaxLifespan(Integer ssoSessionMaxLifespan) { - this.metadata.setSsoSessionMaxLifespan(ssoSessionMaxLifespan); - } - - @Override - public Integer getSsoSessionIdleTimeoutRememberMe() { - return this.metadata.getSsoSessionIdleTimeoutRememberMe(); - } - - @Override - public void setSsoSessionIdleTimeoutRememberMe(Integer ssoSessionIdleTimeoutRememberMe) { - this.metadata.setSsoSessionIdleTimeoutRememberMe(ssoSessionIdleTimeoutRememberMe); - } - - @Override - public Integer getSsoSessionMaxLifespanRememberMe() { - return this.metadata.getSsoSessionMaxLifespanRememberMe(); - } - - @Override - public void setSsoSessionMaxLifespanRememberMe(Integer ssoSessionMaxLifespanRememberMe) { - this.metadata.setSsoSessionMaxLifespanRememberMe(ssoSessionMaxLifespanRememberMe); - } - - @Override - public Integer getOfflineSessionIdleTimeout() { - return this.metadata.getOfflineSessionIdleTimeout(); - } - - @Override - public void setOfflineSessionIdleTimeout(Integer offlineSessionIdleTimeout) { - this.metadata.setOfflineSessionIdleTimeout(offlineSessionIdleTimeout); - } - - @Override - public Integer getAccessTokenLifespan() { - return this.metadata.getAccessTokenLifespan(); - } - - @Override - public void setAccessTokenLifespan(Integer accessTokenLifespan) { - this.metadata.setAccessTokenLifespan(accessTokenLifespan); - } - - @Override - public Integer getAccessTokenLifespanForImplicitFlow() { - return this.metadata.getAccessTokenLifespanForImplicitFlow(); - } - - @Override - public void setAccessTokenLifespanForImplicitFlow(Integer accessTokenLifespanForImplicitFlow) { - this.metadata.setAccessTokenLifespanForImplicitFlow(accessTokenLifespanForImplicitFlow); - } - - @Override - public Integer getAccessCodeLifespan() { - return this.metadata.getAccessCodeLifespan(); - } - - @Override - public void setAccessCodeLifespan(Integer accessCodeLifespan) { - this.metadata.setAccessCodeLifespan(accessCodeLifespan); - } - - @Override - public Integer getAccessCodeLifespanUserAction() { - return this.metadata.getAccessCodeLifespanUserAction(); - } - - @Override - public void setAccessCodeLifespanUserAction(Integer accessCodeLifespanUserAction) { - this.metadata.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction); - } - - @Override - public Integer getAccessCodeLifespanLogin() { - return this.metadata.getAccessCodeLifespanLogin(); - } - - @Override - public void setAccessCodeLifespanLogin(Integer accessCodeLifespanLogin) { - this.metadata.setAccessCodeLifespanLogin(accessCodeLifespanLogin); - } - - @Override - public Long getNotBefore() { - return this.metadata.getNotBefore(); - } - - @Override - public void setNotBefore(Long notBefore) { - this.metadata.setNotBefore(notBefore); - } - - @Override - public Integer getClientSessionIdleTimeout() { - return this.metadata.getClientSessionIdleTimeout(); - } - - @Override - public void setClientSessionIdleTimeout(Integer clientSessionIdleTimeout) { - this.metadata.setClientSessionIdleTimeout(clientSessionIdleTimeout); - } - - @Override - public Integer getClientSessionMaxLifespan() { - return this.metadata.getClientSessionMaxLifespan(); - } - - @Override - public void setClientSessionMaxLifespan(Integer clientSessionMaxLifespan) { - this.metadata.setClientSessionMaxLifespan(clientSessionMaxLifespan); - } - - @Override - public Integer getClientOfflineSessionIdleTimeout() { - return this.metadata.getClientOfflineSessionIdleTimeout(); - } - - @Override - public void setClientOfflineSessionIdleTimeout(Integer clientOfflineSessionIdleTimeout) { - this.metadata.setClientOfflineSessionIdleTimeout(clientOfflineSessionIdleTimeout); - } - - @Override - public Integer getClientOfflineSessionMaxLifespan() { - return this.metadata.getClientOfflineSessionMaxLifespan(); - } - - @Override - public void setClientOfflineSessionMaxLifespan(Integer clientOfflineSessionMaxLifespan) { - this.metadata.setClientOfflineSessionMaxLifespan(clientOfflineSessionMaxLifespan); - } - - @Override - public Integer getActionTokenGeneratedByAdminLifespan() { - return this.metadata.getActionTokenGeneratedByAdminLifespan(); - } - - @Override - public void setActionTokenGeneratedByAdminLifespan(Integer actionTokenGeneratedByAdminLifespan) { - this.metadata.setActionTokenGeneratedByAdminLifespan(actionTokenGeneratedByAdminLifespan); - } - - @Override - public Integer getOfflineSessionMaxLifespan() { - return this.metadata.getOfflineSessionMaxLifespan(); - } - - @Override - public void setOfflineSessionMaxLifespan(Integer offlineSessionMaxLifespan) { - this.metadata.setOfflineSessionMaxLifespan(offlineSessionMaxLifespan); - } - - @Override - public Long getEventsExpiration() { - return this.metadata.getEventsExpiration(); - } - - @Override - public void setEventsExpiration(Long eventsExpiration) { - this.metadata.setEventsExpiration(eventsExpiration); - } - - @Override - public String getPasswordPolicy() { - return this.metadata.getPasswordPolicy(); - } - - @Override - public void setPasswordPolicy(String passwordPolicy) { - this.metadata.setPasswordPolicy(passwordPolicy); - } - - @Override - public String getSslRequired() { - return this.metadata.getSslRequired(); - } - - @Override - public void setSslRequired(String sslRequired) { - this.metadata.setSslRequired(sslRequired); - } - - @Override - public String getLoginTheme() { - return this.metadata.getLoginTheme(); - } - - @Override - public void setLoginTheme(String loginTheme) { - this.metadata.setLoginTheme(loginTheme); - } - - @Override - public String getAccountTheme() { - return this.metadata.getAccountTheme(); - } - - @Override - public void setAccountTheme(String accountTheme) { - this.metadata.setAccountTheme(accountTheme); - } - - @Override - public String getAdminTheme() { - return this.metadata.getAdminTheme(); - } - - @Override - public void setAdminTheme(String adminTheme) { - this.metadata.setAdminTheme(adminTheme); - } - - @Override - public String getEmailTheme() { - return this.metadata.getEmailTheme(); - } - - @Override - public void setEmailTheme(String emailTheme) { - this.metadata.setEmailTheme(emailTheme); - } - - @Override - public String getMasterAdminClient() { - return this.metadata.getMasterAdminClient(); - } - - @Override - public void setMasterAdminClient(String masterAdminClient) { - this.metadata.setMasterAdminClient(masterAdminClient); - } - - @Override - public String getDefaultRoleId() { - return this.metadata.getDefaultRoleId(); - } - - @Override - public void setDefaultRoleId(String defaultRoleId) { - this.metadata.setDefaultRoleId(defaultRoleId); - } - - @Override - public String getDefaultLocale() { - return this.metadata.getDefaultLocale(); - } - - @Override - public void setDefaultLocale(String defaultLocale) { - this.metadata.setDefaultLocale(defaultLocale); - } - - @Override - public String getBrowserFlow() { - return this.metadata.getBrowserFlow(); - } - - @Override - public void setBrowserFlow(String browserFlow) { - this.metadata.setBrowserFlow(browserFlow); - } - - @Override - public String getRegistrationFlow() { - return this.metadata.getRegistrationFlow(); - } - - @Override - public void setRegistrationFlow(String registrationFlow) { - this.metadata.setRegistrationFlow(registrationFlow); - } - - @Override - public String getDirectGrantFlow() { - return this.metadata.getDirectGrantFlow(); - } - - @Override - public void setDirectGrantFlow(String directGrantFlow) { - this.metadata.setDirectGrantFlow(directGrantFlow); - } - - @Override - public String getResetCredentialsFlow() { - return this.metadata.getResetCredentialsFlow(); - } - - @Override - public void setResetCredentialsFlow(String resetCredentialsFlow) { - this.metadata.setResetCredentialsFlow(resetCredentialsFlow); - } - - @Override - public String getClientAuthenticationFlow() { - return this.metadata.getClientAuthenticationFlow(); - } - - @Override - public void setClientAuthenticationFlow(String clientAuthenticationFlow) { - this.metadata.setClientAuthenticationFlow(clientAuthenticationFlow); - } - - @Override - public String getDockerAuthenticationFlow() { - return this.metadata.getDockerAuthenticationFlow(); - } - - @Override - public void setDockerAuthenticationFlow(String dockerAuthenticationFlow) { - this.metadata.setDockerAuthenticationFlow(dockerAuthenticationFlow); - } - - @Override - public MapOTPPolicyEntity getOTPPolicy() { - return this.metadata.getOTPPolicy(); - } - - @Override - public void setOTPPolicy(MapOTPPolicyEntity otpPolicy) { - this.metadata.setOTPPolicy(otpPolicy); - } - - @Override - public MapWebAuthnPolicyEntity getWebAuthnPolicy() { - return metadata.getWebAuthnPolicy(); - } - - @Override - public void setWebAuthnPolicy(MapWebAuthnPolicyEntity webAuthnPolicy) { - this.metadata.setWebAuthnPolicy(webAuthnPolicy); - } - - @Override - public MapWebAuthnPolicyEntity getWebAuthnPolicyPasswordless() { - return this.metadata.getWebAuthnPolicyPasswordless(); - } - - @Override - public void setWebAuthnPolicyPasswordless(MapWebAuthnPolicyEntity webAuthnPolicyPasswordless) { - this.metadata.setWebAuthnPolicyPasswordless(webAuthnPolicyPasswordless); - } - - @Override - public Set getDefaultClientScopeIds() { - return this.metadata.getDefaultClientScopeIds(); - } - - @Override - public void addDefaultClientScopeId(String scopeId) { - this.metadata.addDefaultClientScopeId(scopeId); - } - - @Override - public Boolean removeDefaultClientScopeId(String scopeId) { - return this.metadata.removeDefaultClientScopeId(scopeId); - } - - @Override - public Set getOptionalClientScopeIds() { - return this.metadata.getOptionalClientScopeIds(); - } - - @Override - public void addOptionalClientScopeId(String scopeId) { - this.metadata.addOptionalClientScopeId(scopeId); - } - - @Override - public Boolean removeOptionalClientScopeId(String scopeId) { - return this.metadata.removeOptionalClientScopeId(scopeId); - } - - @Override - public Set getDefaultGroupIds() { - return this.metadata.getDefaultGroupIds(); - } - - @Override - public void addDefaultGroupId(String groupId) { - this.metadata.addDefaultGroupId(groupId); - } - - @Override - public void removeDefaultGroupId(String groupId) { - this.metadata.removeDefaultGroupId(groupId); - } - - @Override - public Set getEventsListeners() { - return this.metadata.getEventsListeners(); - } - - @Override - public void setEventsListeners(Set eventsListeners) { - this.metadata.setEventsListeners(eventsListeners); - } - - @Override - public Set getEnabledEventTypes() { - return this.metadata.getEnabledEventTypes(); - } - - @Override - public void setEnabledEventTypes(Set enabledEventTypes) { - this.metadata.setEnabledEventTypes(enabledEventTypes); - } - - @Override - public Set getSupportedLocales() { - return this.metadata.getSupportedLocales(); - } - - @Override - public void setSupportedLocales(Set supportedLocales) { - this.metadata.setSupportedLocales(supportedLocales); - } - - @Override - public Map> getLocalizationTexts() { - return this.metadata.getLocalizationTexts(); - } - - @Override - public Map getLocalizationText(String locale) { - return this.metadata.getLocalizationText(locale); - } - - @Override - public void setLocalizationText(String locale, Map texts) { - this.metadata.setLocalizationText(locale, texts); - } - - @Override - public Boolean removeLocalizationText(String locale) { - return this.metadata.removeLocalizationText(locale); - } - - @Override - public Map getBrowserSecurityHeaders() { - return this.metadata.getBrowserSecurityHeaders(); - } - - @Override - public void setBrowserSecurityHeaders(Map headers) { - this.metadata.setBrowserSecurityHeaders(headers); - } - - @Override - public void setBrowserSecurityHeader(String name, String value) { - this.metadata.setBrowserSecurityHeader(name, value); - } - - @Override - public Map getSmtpConfig() { - return this.metadata.getSmtpConfig(); - } - - @Override - public void setSmtpConfig(Map smtpConfig) { - this.metadata.setSmtpConfig(smtpConfig); - } - - @Override - public Set getRequiredCredentials() { - return this.metadata.getRequiredCredentials(); - } - - @Override - public void addRequiredCredential(MapRequiredCredentialEntity requiredCredential) { - this.metadata.addRequiredCredential(requiredCredential); - } - - @Override - public Set getComponents() { - return this.components.stream().map(MapComponentEntity.class::cast).collect(Collectors.toSet()); - } - - @Override - public Optional getComponent(String componentId) { - return this.components.stream().filter(c -> Objects.equals(c.getId(), componentId)).findFirst().map(MapComponentEntity.class::cast); - } - - @Override - public void addComponent(MapComponentEntity component) { - JpaComponentEntity jpaComponent = JpaComponentEntity.class.cast(CLONER.from(component)); - jpaComponent.setParent(this); - jpaComponent.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_COMPONENT); - this.components.add(jpaComponent); - } - - @Override - public Boolean removeComponent(String componentId) { - return this.components.removeIf(c -> Objects.equals(c.getId(), componentId)); - } - - @Override - public Set getAuthenticationFlows() { - return this.metadata.getAuthenticationFlows(); - } - - @Override - public Optional getAuthenticationFlow(String p0) { - return metadata.getAuthenticationFlow(p0); - } - - @Override - public void addAuthenticationFlow(MapAuthenticationFlowEntity authenticationFlow) { - this.metadata.addAuthenticationFlow(authenticationFlow); - } - - @Override - public Boolean removeAuthenticationFlow(String p0) { - return metadata.removeAuthenticationFlow(p0); - } - - @Override - public Set getAuthenticationExecutions() { - return this.metadata.getAuthenticationExecutions(); - } - - public Optional getAuthenticationExecution(String p0) { - return metadata.getAuthenticationExecution(p0); - } - - @Override - public void addAuthenticationExecution(MapAuthenticationExecutionEntity authenticationExecution) { - this.metadata.addAuthenticationExecution(authenticationExecution); - } - - @Override - public Boolean removeAuthenticationExecution(String p0) { - return metadata.removeAuthenticationExecution(p0); - } - - @Override - public Set getAuthenticatorConfigs() { - return this.metadata.getAuthenticatorConfigs(); - } - - @Override - public Optional getAuthenticatorConfig(String p0) { - return metadata.getAuthenticatorConfig(p0); - } - - @Override - public void addAuthenticatorConfig(MapAuthenticatorConfigEntity authenticatorConfig) { - this.metadata.addAuthenticatorConfig(authenticatorConfig); - } - - @Override - public Boolean removeAuthenticatorConfig(String p0) { - return metadata.removeAuthenticatorConfig(p0); - } - - @Override - public Set getRequiredActionProviders() { - return this.metadata.getRequiredActionProviders(); - } - - @Override - public Optional getRequiredActionProvider(String requiredActionProviderId) { - return this.metadata.getRequiredActionProvider(requiredActionProviderId); - } - - @Override - public void addRequiredActionProvider(MapRequiredActionProviderEntity requiredActionProvider) { - this.metadata.addRequiredActionProvider(requiredActionProvider); - } - - @Override - public Boolean removeRequiredActionProvider(String requiredActionProviderId) { - return this.metadata.removeRequiredActionProvider(requiredActionProviderId); - } - - @Override - public Set getIdentityProviders() { - return this.metadata.getIdentityProviders(); - } - - @Override - public void addIdentityProvider(MapIdentityProviderEntity identityProvider) { - this.metadata.addIdentityProvider(identityProvider); - } - - @Override - public Boolean removeIdentityProvider(String p0) { - return metadata.removeIdentityProvider(p0); - } - - @Override - public void addIdentityProviderMapper(MapIdentityProviderMapperEntity identityProviderMapper) { - this.metadata.addIdentityProviderMapper(identityProviderMapper); - } - - @Override - public Set getIdentityProviderMappers() { - return this.metadata.getIdentityProviderMappers(); - } - - @Override - public Optional getIdentityProviderMapper(String p0) { - return metadata.getIdentityProviderMapper(p0); - } - - @Override - public Boolean removeIdentityProviderMapper(String p0) { - return metadata.removeIdentityProviderMapper(p0); - } - - @Override - public Set getClientInitialAccesses() { - return this.metadata.getClientInitialAccesses(); - } - - @Override - public Optional getClientInitialAccess(String p0) { - return metadata.getClientInitialAccess(p0); - } - - @Override - public void addClientInitialAccess(MapClientInitialAccessEntity clientInitialAccess) { - this.metadata.addClientInitialAccess(clientInitialAccess); - } - - @Override - public Boolean removeClientInitialAccess(String p0) { - return metadata.removeClientInitialAccess(p0); - } - - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaRealmAttributeEntity attribute : this.attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - for (Map.Entry> attrEntry : attributes.entrySet()) { - this.setAttribute(attrEntry.getKey(), attrEntry.getValue()); - } - } - } - - @Override - public List getAttribute(String name) { - return this.attributes.stream() - .filter(a -> Objects.equals(a.getName(), name)) - .map(JpaRealmAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public void setAttribute(String name, List values) { - this.removeAttribute(name); - for (String value : values) { - JpaRealmAttributeEntity attribute = new JpaRealmAttributeEntity(this, name, value); - this.attributes.add(attribute); - } - } - - @Override - public void removeAttribute(String name) { - this.attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaRealmEntity)) return false; - return Objects.equals(getId(), ((JpaRealmEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmMetadata.java deleted file mode 100644 index 8c65366be37..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/entity/JpaRealmMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.realm.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.realm.MapRealmEntityImpl; - -/** - * Class that contains all the realm metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaRealmMetadata extends MapRealmEntityImpl implements Serializable { - - public JpaRealmMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - public JpaRealmMetadata(final DeepCloner cloner) { - super(cloner); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapStorage.java deleted file mode 100644 index ae05454f31a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapStorage.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.role.MapRoleEntity; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE; -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER; - -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.role.delegate.JpaMapRoleEntityDelegate; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity; - -public class JpaRoleMapStorage extends JpaMapStorage { - - private static final Logger logger = Logger.getLogger(JpaRoleMapStorage.class); - - @SuppressWarnings("unchecked") - public JpaRoleMapStorage(KeycloakSession session, EntityManager em) { - super(session, JpaRoleEntity.class, RoleModel.class, em); - } - - @Override - public Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaRoleEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("clientId"), - root.get("name"), - root.get("description") - ); - } - - @Override - public void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_ROLE); - } - - @Override - public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaRoleModelCriteriaBuilder(); - } - - @Override - public MapRoleEntity create(MapRoleEntity mapEntity) { - JpaRoleEntity jpaEntity = new JpaRoleEntity(CLONER); - MapRoleEntity entity = new JpaMapRoleEntityDelegate(jpaEntity, em); - CLONER.deepClone(mapEntity, entity); - logger.tracef("tx %d: create entity %s", hashCode(), jpaEntity.getId()); - setEntityVersion(jpaEntity); - return mapToEntityDelegateUnique(jpaEntity); - } - - @Override - protected MapRoleEntity mapToEntityDelegate(JpaRoleEntity original) { - return new JpaMapRoleEntityDelegate(original, em); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java deleted file mode 100644 index ed973a63af4..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role; - -import java.util.Arrays; -import java.util.Set; -import java.util.UUID; -import jakarta.persistence.criteria.CriteriaBuilder.In; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Subquery; - -import org.keycloak.models.RoleModel; -import org.keycloak.models.RoleModel.SearchableFields; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntity; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaRoleModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaRoleModelCriteriaBuilder() { - super(JpaRoleModelCriteriaBuilder::new); - } - - private JpaRoleModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaRoleModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaRoleModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.CLIENT_ID || - modelField == SearchableFields.NAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaRoleModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.COMPOSITE_ROLE) { - - validateValue(value, modelField, op, String.class); - - return new JpaRoleModelCriteriaBuilder((cb, query, root) -> { - - // using a sub-query here instead of a join as a sub-query will work also for deletions. - Subquery subquery = query.subquery(UUID.class); - Root subRoot = subquery.from(JpaRoleCompositeEntity.class); - subquery.select(subRoot.get("key").get("roleId")); - In roleChildId = cb.in(subRoot.get("key").get("childRoleId")); - Arrays.stream(value).forEach(roleChildId::value); - subquery.where(roleChildId); - - In in = cb.in(root.get("id")); - in.value(subquery); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case IN: - if (modelField == SearchableFields.ID) { - - Set uuids = getUuidsForInOperator(value, modelField); - - if (uuids.isEmpty()) return new JpaRoleModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaRoleModelCriteriaBuilder((cb, query, root) -> { - In in = cb.in(root.get("id")); - uuids.forEach(in::value); - return in; - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case ILIKE: - if (modelField == SearchableFields.NAME || - modelField == SearchableFields.DESCRIPTION) { - - validateValue(value, modelField, op, String.class); - - return new JpaRoleModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase()) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case NOT_EXISTS: - if (modelField == SearchableFields.CLIENT_ID) { - return new JpaRoleModelCriteriaBuilder((cb, query, root) -> cb.isNull(root.get("clientId"))); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/delegate/JpaMapRoleEntityDelegate.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/delegate/JpaMapRoleEntityDelegate.java deleted file mode 100644 index 0b1acd01ee7..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/delegate/JpaMapRoleEntityDelegate.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.delegate; - -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.role.MapRoleEntityDelegate; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntity; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntityKey; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.Query; -import jakarta.persistence.TypedQuery; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Delegate for the JPA implementation for MapRoleEntityDelegate. - * It will delegate all access to the composite roles to a separate table. - * - * For performance reasons, it caches the composite roles within the session if they have already been retrieved. - * This relies on the behavior of {@link JpaMapStorage} that - * each entity is created only once within each session. - * - * @author Alexander Schwartz - */ -public class JpaMapRoleEntityDelegate extends MapRoleEntityDelegate { - private final EntityManager em; - private final JpaRoleEntity original; - - private Set compositeRoles; - - @Override - public void setId(String id) { - if (super.getId() == null) { - super.setId(id); - // As the entity will be used when creating the composite roles, it needs to be persisted before that. - // The ID not being set indicates a new entity that hasn't been persisted yet, and the ID is the minimum field for persisting it. - em.persist(original); - } else { - super.setId(id); - } - } - - public JpaMapRoleEntityDelegate(JpaRoleEntity original, EntityManager em) { - super(new JpaRoleDelegateProvider(original, em)); - this.original = original; - this.em = em; - } - - @Override - public Set getCompositeRoles() { - if (compositeRoles == null) { - TypedQuery query = em.createNamedQuery("selectChildRolesFromCompositeRole", JpaRoleCompositeEntityKey.class); - query.setParameter("roleId", StringKeyConverter.UUIDKey.INSTANCE.fromString(getId())); - compositeRoles = query.getResultList().stream().map(JpaRoleCompositeEntityKey::getChildRoleId).collect(Collectors.toSet()); - } - return compositeRoles; - } - - @Override - public void setCompositeRoles(Set compositeRoles) { - Query query = em.createNamedQuery("deleteAllChildRolesFromCompositeRole"); - query.setParameter("roleId", StringKeyConverter.UUIDKey.INSTANCE.fromString(getId())); - query.executeUpdate(); - if (compositeRoles != null) { - compositeRoles.forEach(this::addCompositeRole); - } - this.compositeRoles = compositeRoles; - } - - @Override - public void addCompositeRole(String roleId) { - JpaRoleCompositeEntityKey key = new JpaRoleCompositeEntityKey(getId(), roleId); - if (compositeRoles != null) { - if (compositeRoles.contains(roleId)) { - return; - } - } else { - if (em.find(JpaRoleCompositeEntity.class, key) != null) { - return; - } - } - JpaRoleCompositeEntity entity = new JpaRoleCompositeEntity(key); - em.persist(entity); - if (compositeRoles != null) { - compositeRoles.add(roleId); - } - } - - @Override - public void removeCompositeRole(String roleId) { - Query query = em.createNamedQuery("deleteChildRoleFromCompositeRole"); - query.setParameter("roleId", StringKeyConverter.UUIDKey.INSTANCE.fromString(getId())); - query.setParameter("childRoleId", roleId); - query.executeUpdate(); - if (compositeRoles != null) { - compositeRoles.remove(roleId); - } - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/delegate/JpaRoleDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/delegate/JpaRoleDelegateProvider.java deleted file mode 100644 index a5fd95ef28f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/delegate/JpaRoleDelegateProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.delegate; - -import java.util.UUID; -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.role.MapRoleEntityFields; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity; - -public class JpaRoleDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaRoleDelegateProvider(JpaRoleEntity delegate, EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public JpaRoleEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapRoleEntityFields) { - switch ((MapRoleEntityFields) field) { - case ID: - case REALM_ID: - case CLIENT_ID: - case NAME: - case DESCRIPTION: - return getDelegate(); - - case ATTRIBUTES: - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaRoleEntity.class); - Root root = query.from(JpaRoleEntity.class); - root.fetch("attributes", JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - - setDelegate(em.createQuery(query).getSingleResult()); - break; - - default: - setDelegate(em.find(JpaRoleEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid role field: " + field); - } - } else { - setDelegate(em.find(JpaRoleEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleAttributeEntity.java deleted file mode 100644 index b08249c0cdd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleAttributeEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -@Entity -@Table(name = "kc_role_attribute", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaRoleAttributeEntity extends JpaAttributeEntityWithHashValue { - - public JpaRoleAttributeEntity() { - } - - public JpaRoleAttributeEntity(JpaRoleEntity root, String name, String value) { - super(root, name, value); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleCompositeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleCompositeEntity.java deleted file mode 100644 index 9b3bb33101c..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleCompositeEntity.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.entity; - -import org.hibernate.annotations.Immutable; - -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -/** - * This is a child table of {@link JpaRoleEntity} that is managed via named queries to avoid loading all its contents - * via a {@link jakarta.persistence.OneToMany} relation. - */ -@Entity -@Table(name = "kc_role_composite", uniqueConstraints = {@UniqueConstraint(columnNames = {"role_id", "child_role_id"})}) -@Immutable -@NamedQueries({ - @NamedQuery(name = "selectChildRolesFromCompositeRole", - query = "select rolecomposite.key from JpaRoleCompositeEntity rolecomposite where rolecomposite.key.roleId = :roleId"), - @NamedQuery(name = "deleteChildRoleFromCompositeRole", - query = "delete from JpaRoleCompositeEntity rolecomposite where rolecomposite.key.roleId = :roleId and rolecomposite.key.childRoleId = :childRoleId"), - @NamedQuery(name = "deleteAllChildRolesFromCompositeRole", - query = "delete from JpaRoleCompositeEntity rolecomposite where rolecomposite.key.roleId = :roleId"), -}) -public class JpaRoleCompositeEntity { - - @EmbeddedId - private JpaRoleCompositeEntityKey key = new JpaRoleCompositeEntityKey(); - - public JpaRoleCompositeEntity() { - } - - public JpaRoleCompositeEntity(JpaRoleCompositeEntityKey key) { - this.key = key; - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null) return false; - if (!(o instanceof JpaRoleCompositeEntity)) return false; - - JpaRoleCompositeEntity that = (JpaRoleCompositeEntity) o; - - if (!key.equals(that.key)) return false; - - return true; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleCompositeEntityKey.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleCompositeEntityKey.java deleted file mode 100644 index 8a04e0f9c3c..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleCompositeEntityKey.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.entity; - -import org.keycloak.models.map.common.StringKeyConverter; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import java.io.Serializable; -import java.util.Objects; -import java.util.UUID; - -/** - * The composite primary key representation for {@link JpaRoleCompositeEntity}. - * Required for performing a lookup by primary key through the JPA entity manager. - */ -@Embeddable -public class JpaRoleCompositeEntityKey implements Serializable { - - @Column(name = "role_id") - private UUID roleId; - - public UUID getRoleId() { - return roleId; - } - - public String getChildRoleId() { - return childRoleId; - } - - @Column(name = "child_role_id") - private String childRoleId; - - public JpaRoleCompositeEntityKey() { - } - - public JpaRoleCompositeEntityKey(String roleId, String childRoleId) { - this.roleId = StringKeyConverter.UUIDKey.INSTANCE.fromString(roleId); - this.childRoleId = childRoleId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null) return false; - if (!(o instanceof JpaRoleCompositeEntityKey)) return false; - - JpaRoleCompositeEntityKey that = (JpaRoleCompositeEntityKey) o; - - if (!roleId.equals(that.roleId)) return false; - if (!childRoleId.equals(that.childRoleId)) return false; - - return true; - } - - @Override - public int hashCode() { - return Objects.hash(roleId, childRoleId); - } - -} \ No newline at end of file diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleEntity.java deleted file mode 100644 index 36c8a428cf6..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleEntity.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.entity; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_role", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId", "name"})}) -public class JpaRoleEntity extends AbstractRoleEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaRoleMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String clientId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String name; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String description; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaRoleEntity() { - this.metadata = new JpaRoleMetadata(); - } - - public JpaRoleEntity(DeepCloner cloner) { - this.metadata = new JpaRoleMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select role without metadata(json) field. - */ - public JpaRoleEntity(UUID id, int version, Integer entityVersion, String realmId, String clientId, String name, String description) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.clientId = clientId; - this.name = name; - this.description = description; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_ROLE; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public String getClientId() { - if (isMetadataInitialized()) return metadata.getClientId(); - return clientId; - } - - @Override - public String getName() { - if (isMetadataInitialized()) return metadata.getName(); - return name; - } - - @Override - public String getDescription() { - if (isMetadataInitialized()) return metadata.getDescription(); - return description; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public void setClientId(String clientId) { - metadata.setClientId(clientId); - } - - @Override - public void setName(String name) { - metadata.setName(name); - } - - @Override - public void setDescription(String description) { - metadata.setDescription(description); - } - - @Override - public Set getCompositeRoles() { - throw new UnsupportedOperationException("this is implemented in JpaMapRoleEntityDelegate, should never be called"); - } - - @Override - public void setCompositeRoles(Set compositeRoles) { - if (compositeRoles == null) { - // this is called when cloning an entity during creation, can't be avoided with the current implementation - return; - } - throw new UnsupportedOperationException("this is implemented in JpaMapRoleEntityDelegate, should never be called"); - - } - - @Override - public void addCompositeRole(String roleId) { - throw new UnsupportedOperationException("this is implemented in JpaMapRoleEntityDelegate, should never be called"); - - } - - @Override - public void removeCompositeRole(String roleId) { - throw new UnsupportedOperationException("this is implemented in JpaMapRoleEntityDelegate, should never be called"); - } - - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaRoleAttributeEntity attribute : attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public List getAttribute(String name) { - return attributes.stream() - .filter(a -> Objects.equals(a.getName(), name)) - .map(JpaRoleAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - for (Map.Entry> entry : attributes.entrySet()) { - setAttribute(entry.getKey(), entry.getValue()); - } - } - } - - @Override - public void setAttribute(String name, List values) { - removeAttribute(name); - for (String value : values) { - JpaRoleAttributeEntity attribute = new JpaRoleAttributeEntity(this, name, value); - attributes.add(attribute); - } - } - - @Override - public void removeAttribute(String name) { - attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaRoleEntity)) return false; - return Objects.equals(getId(), ((JpaRoleEntity) obj).getId()); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleMetadata.java deleted file mode 100644 index 69f06e8f976..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/entity/JpaRoleMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.role.entity; - -import java.io.Serializable; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.role.MapRoleEntityImpl; - -public class JpaRoleMetadata extends MapRoleEntityImpl implements Serializable { - - public JpaRoleMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaRoleMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapStorage.java deleted file mode 100644 index 1bc44df53a1..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.singleUseObject; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT; - -/** - * A {@link MapStorage} implementation for single-use object entities. - * - * @author Stefan Guilhen - */ -public class JpaSingleUseObjectMapStorage extends JpaMapStorage { - - public JpaSingleUseObjectMapStorage(KeycloakSession session, final EntityManager em) { - super(session, JpaSingleUseObjectEntity.class, SingleUseObjectValueModel.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return root; - } - - @Override - protected void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT); - } - - @Override - protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaSingleUseObjectModelCriteriaBuilder(); - } - - @Override - protected MapSingleUseObjectEntity mapToEntityDelegate(JpaSingleUseObjectEntity original) { - return original; - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java deleted file mode 100644 index ef01735408e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.singleUseObject; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity; -import org.keycloak.storage.SearchableModelField; - -/** - * A {@link JpaModelCriteriaBuilder} implementation for single-use objects. - * - * @author Stefan Guilhen - */ -public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaSingleUseObjectModelCriteriaBuilder() { - super(JpaSingleUseObjectModelCriteriaBuilder::new); - } - - public JpaSingleUseObjectModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaSingleUseObjectModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaSingleUseObjectModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if(modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) { - validateValue(value, modelField, op, String.class); - return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectEntity.java deleted file mode 100644 index 65d6a53f8c3..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectEntity.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.singleUseObject.entity; - -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; - -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT; - -/** - * JPA {@link MapSingleUseObjectEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_single_use_obj") -public class JpaSingleUseObjectEntity extends MapSingleUseObjectEntity.AbstractSingleUseObjectEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaSingleUseObjectMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String objectKey; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long expiration; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set notes = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaSingleUseObjectEntity() { - this.metadata = new JpaSingleUseObjectMetadata(); - } - - public JpaSingleUseObjectEntity(final DeepCloner cloner) { - this.metadata = new JpaSingleUseObjectMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return this.metadata != null; - } - - @Override - public int getVersion() { - return this.version; - } - - @Override - public Integer getEntityVersion() { - if (this.isMetadataInitialized()) return this.metadata.getEntityVersion(); - return this.entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - this.metadata.setEntityVersion(entityVersion); - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getObjectKey() { - if (this.isMetadataInitialized()) return this.metadata.getObjectKey(); - return this.objectKey; - } - - @Override - public void setObjectKey(final String objectKey) { - this.metadata.setObjectKey(objectKey); - } - - @Override - public Integer getCurrentSchemaVersion() { - return CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT; - } - - @Override - public Map getNotes() { - return this.notes.stream() - .collect(Collectors.toMap(JpaSingleUseObjectNoteEntity::getName, JpaSingleUseObjectNoteEntity::getValue)); - } - - @Override - public String getNote(String name) { - return this.notes.stream().filter(note -> Objects.equals(note.getName(), name)) - .findFirst() - .map(JpaSingleUseObjectNoteEntity::getValue) - .orElse(null); - } - - @Override - public void setNotes(Map notes) { - this.notes.clear(); - if (notes != null) { - notes.forEach(this::setNote); - } - } - - @Override - public void setNote(String name, String value) { - if (name != null) { - this.notes.removeIf(note -> Objects.equals(note.getName(), name)); - if (value != null && !value.trim().isEmpty()) - this.notes.add(new JpaSingleUseObjectNoteEntity(this, name, value)); - } - } - - @Override - public Long getExpiration() { - if (this.isMetadataInitialized()) return this.metadata.getExpiration(); - return this.expiration; - } - - @Override - public void setExpiration(Long expiration) { - this.metadata.setExpiration(expiration); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaSingleUseObjectEntity)) return false; - return Objects.equals(getId(), ((JpaSingleUseObjectEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectMetadata.java deleted file mode 100644 index dcfe45f0e36..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.singleUseObject.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntityImpl; - -/** - * Class that contains all the single-use object metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaSingleUseObjectMetadata extends MapSingleUseObjectEntityImpl implements Serializable { - - public JpaSingleUseObjectMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - public JpaSingleUseObjectMetadata(final DeepCloner cloner) { - super(cloner); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectNoteEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectNoteEntity.java deleted file mode 100644 index e3ec9750eed..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/entity/JpaSingleUseObjectNoteEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.singleUseObject.entity; - -import java.util.Objects; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntity; - -/** - * JPA implementation for single-use object notes. This entity represents a note and has a many-to-one relationship - * with the single-use object entity. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_single_use_obj_note", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name"}) -}) -public class JpaSingleUseObjectNoteEntity extends JpaAttributeEntity { - - public JpaSingleUseObjectNoteEntity() { - } - - public JpaSingleUseObjectNoteEntity(final JpaSingleUseObjectEntity root, final String name, final String value) { - super(root, name, value); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaSingleUseObjectNoteEntity)) return false; - JpaSingleUseObjectNoteEntity that = (JpaSingleUseObjectNoteEntity) obj; - return Objects.equals(getParent(), that.getParent()) && - Objects.equals(getName(), that.getName()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterProvider.java deleted file mode 100644 index 5d120067e72..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterProvider.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.storage.jpa.updater; - -import org.keycloak.provider.Provider; - -import java.io.File; -import java.sql.Connection; - -public interface MapJpaUpdaterProvider extends Provider { - - /** - * Status of database up-to-dateness - */ - enum Status { - /** - * Database is valid and up to date - */ - VALID, - /** - * No database exists. - */ - EMPTY, - /** - * Database needs to be updated - */ - OUTDATED - } - - /** - * Updates the Keycloak database for the given model type - * @param modelType Model type - * @param connection DB connection - * @param defaultSchema DB connection - */ - void update(Class modelType, Connection connection, String defaultSchema); - - /** - * Checks whether Keycloak database for the given model type is up to date with the most recent changesets - * @param modelType Model type - * @param connection DB connection - * @param defaultSchema DB schema to use - * @return - */ - Status validate(Class modelType, Connection connection, String defaultSchema); - - /** - * Exports the SQL update script for the given model type into the given File. - * @param modelType Model type - * @param connection DB connection - * @param defaultSchema DB schema to use - * @param file File to write to - */ - void export(Class modelType, Connection connection, String defaultSchema, File file); - - /** - * Returns an all-lower-case short name of the used database. - */ - String getDatabaseShortName(); -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterProviderFactory.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterProviderFactory.java deleted file mode 100644 index 3280fdc469d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterProviderFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.storage.jpa.updater; - -import org.keycloak.provider.ProviderFactory; - -public interface MapJpaUpdaterProviderFactory extends ProviderFactory { -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterSpi.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterSpi.java deleted file mode 100644 index e325d8f8c2f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/updater/MapJpaUpdaterSpi.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.storage.jpa.updater; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class MapJpaUpdaterSpi implements Spi { - - public final static String NAME = "mapJpaUpdater"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return MapJpaUpdaterProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return MapJpaUpdaterProviderFactory.class; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapStorage.java deleted file mode 100644 index 4b944128d1e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapStorage.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.UserModel; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.user.delegate.JpaUserDelegateProvider; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.user.MapUserEntityDelegate; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER; - -/** - * A {@link MapStorage} implementation for user entities. - * - * @author Stefan Guilhen - */ -public class JpaUserMapStorage extends JpaMapStorage { - - public JpaUserMapStorage(KeycloakSession session, final EntityManager em) { - super(session, JpaUserEntity.class, UserModel.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return cb.construct(JpaUserEntity.class, - root.get("id"), - root.get("version"), - root.get("entityVersion"), - root.get("realmId"), - root.get("username"), - root.get("usernameWithCase"), - root.get("firstName"), - root.get("lastName"), - root.get("email"), - root.get("emailConstraint"), - root.get("federationLink"), - root.get("enabled"), - root.get("emailVerified"), - root.get("timestamp") - ); - } - - @Override - protected void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_USER); - } - - @Override - protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaUserModelCriteriaBuilder(); - } - - @Override - protected MapUserEntity mapToEntityDelegate(JpaUserEntity original) { - return new MapUserEntityDelegate(new JpaUserDelegateProvider(original, this.em)); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java deleted file mode 100644 index b7706f97c18..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user; - -import java.util.Arrays; -import java.util.Collection; - -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; - -import org.keycloak.models.UserModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityEntity; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.storage.StorageId; - -/** - * A {@link JpaModelCriteriaBuilder} implementation for users. - * - * @author Stefan Guilhen - */ -public class JpaUserModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - private static final char ESCAPE_BACKSLASH = '\\'; - - public JpaUserModelCriteriaBuilder() { - super(JpaUserModelCriteriaBuilder::new); - } - - private JpaUserModelCriteriaBuilder(final JpaPredicateFunction predicateFunc) { - super(JpaUserModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaUserModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch(op) { - case EQ: - if (modelField == UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get("username"), value[0].toString().toLowerCase()) - ); - - } else if (modelField == UserModel.SearchableFields.USERNAME) { - validateValue(value, modelField, op, String.class); - - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.or( - cb.and( - cb.equal(root.get("usernameWithCase"), value[0]), - cb.ge(root.get("entityVersion"), 2) - ), - cb.and( - cb.equal(root.get("username"), value[0].toString().toLowerCase()), - cb.le(root.get("entityVersion"), 1) - ) - ) - ); - - } else if (modelField == UserModel.SearchableFields.REALM_ID || - modelField == UserModel.SearchableFields.EMAIL || - modelField == UserModel.SearchableFields.FEDERATION_LINK) { - - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - - } else if (modelField == UserModel.SearchableFields.ENABLED || - modelField == UserModel.SearchableFields.EMAIL_VERIFIED) { - - validateValue(value, modelField, op, Boolean.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - - } else if (modelField == UserModel.SearchableFields.IDP_AND_USER) { - - if (value == null || value.length == 0 || value.length > 2) { - throw new CriterionNotSupportedException(modelField, op, - "Invalid arguments, expected (idp_alias) or (idp_alias, idp_user), got: " + Arrays.toString(value)); - } - - if (value.length == 1) { - // search by idp only - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.join("federatedIdentities", JoinType.LEFT).get("identityProvider"), - value[0])); - } else if (value[0] == null) { - // search by userid only - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.join("federatedIdentities", JoinType.LEFT).get("userId"), - value[1])); - } else { - // search using both idp and userid - return new JpaUserModelCriteriaBuilder((cb, query, root) -> { - Join join = - root.join("federatedIdentities", JoinType.LEFT); - return cb.and(cb.equal(join.get("identityProvider"), value[0]), - cb.equal(join.get("userId"),value[1])); - }); - } - - } else if (modelField == UserModel.SearchableFields.ASSIGNED_GROUP) { - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.join("groupIds", JoinType.LEFT), value[0])); - - } else if (modelField == UserModel.SearchableFields.ASSIGNED_ROLE) { - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.join("roleIds", JoinType.LEFT), value[0])); - - } else if (modelField == UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT) { - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal( - cb.function("->>", String.class, root.get("metadata"), cb.literal("fServiceAccountClientLink")), - value[0])); - - } else if (modelField == UserModel.SearchableFields.CONSENT_FOR_CLIENT) { - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.join("consents", JoinType.LEFT).get("clientId"), value[0])); - - } else if (modelField == UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK) { - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> { - String providerId = new StorageId((String) value[0], "").getId() + "%"; - return cb.like(root.join("consents", JoinType.LEFT).get("clientId"), providerId); - }); - - } else if (modelField == UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE) { - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("consents", JoinType.LEFT); - return cb.isTrue(cb.function("@>", - Boolean.TYPE, - cb.function("->", JsonbType.class, join.get("metadata"), cb.literal("fGrantedClientScopesIds")), - cb.literal(convertToJson(value[0])))); - }); - - } else if (modelField == UserModel.SearchableFields.ATTRIBUTE) { - validateValue(value, modelField, op, String.class, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("attributes", JoinType.LEFT); - return cb.and( - cb.equal(join.get("name"), value[0]), - hashExpression(cb, join, "value_hash", value[1]), - cb.equal(join.get("value"), value[1]) - ); - }); - } - else { - throw new CriterionNotSupportedException(modelField, op); - } - case ILIKE: - if (modelField == UserModel.SearchableFields.FIRST_NAME || - modelField == UserModel.SearchableFields.LAST_NAME || - modelField == UserModel.SearchableFields.EMAIL) { - - validateValue(value, modelField, op, String.class); - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase())); - - } else if (modelField == UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.like(root.get("username"), value[0].toString().toLowerCase(), ESCAPE_BACKSLASH) - ); - - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case LIKE: - if (modelField == UserModel.SearchableFields.USERNAME) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.or( - cb.and( - cb.like(root.get("usernameWithCase"), value[0].toString(), ESCAPE_BACKSLASH), - cb.ge(root.get("entityVersion"), 2) - ), - cb.and( - cb.like(root.get("username"), value[0].toString(), ESCAPE_BACKSLASH), - cb.le(root.get("entityVersion"), 1) - ) - ) - ); - - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case IN: - if (modelField == UserModel.SearchableFields.ASSIGNED_GROUP) { - final Collection collectionValues = getValuesForInOperator(value, modelField); - if (collectionValues.isEmpty()) return new JpaUserModelCriteriaBuilder((cb, query, root) -> cb.or()); - - return new JpaUserModelCriteriaBuilder((cb, query, root) -> { - CriteriaBuilder.In in = cb.in(root.join("groupIds", JoinType.LEFT)); - collectionValues.forEach(groupId -> { - if (!(groupId instanceof String)) - throw new CriterionNotSupportedException(modelField, op, "Invalid type. Expected String, got " + groupId.getClass()); - in.value(groupId.toString()); - }); - return in; - }); - - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case NOT_EXISTS: - if (modelField == UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT) { - return new JpaUserModelCriteriaBuilder((cb, query, root) -> - cb.isNull(cb.function("->>", String.class, root.get("metadata"), cb.literal("fServiceAccountClientLink")))); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/delegate/JpaUserDelegateProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/delegate/JpaUserDelegateProvider.java deleted file mode 100644 index 0f423e03338..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/delegate/JpaUserDelegateProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.delegate; - -import java.util.UUID; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.storage.jpa.JpaDelegateProvider; -import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.user.MapUserEntityFields; - -/** - * A {@link DelegateProvider} implementation for {@link JpaUserEntity}. - * - * @author Stefan Guilhen - */ -public class JpaUserDelegateProvider extends JpaDelegateProvider implements DelegateProvider { - - private final EntityManager em; - - public JpaUserDelegateProvider(final JpaUserEntity delegate, final EntityManager em) { - super(delegate); - this.em = em; - } - - @Override - public MapUserEntity getDelegate(boolean isRead, Enum> field, Object... parameters) { - if (getDelegate().isMetadataInitialized()) return getDelegate(); - if (isRead) { - if (field instanceof MapUserEntityFields) { - switch ((MapUserEntityFields) field) { - case ID: - case REALM_ID: - case USERNAME: - case FIRST_NAME: - case LAST_NAME: - case EMAIL: - case EMAIL_CONSTRAINT: - case FEDERATION_LINK: - case ENABLED: - case EMAIL_VERIFIED: - case CREATED_TIMESTAMP: - return getDelegate(); - - case ATTRIBUTES: - this.setDelegateWithAssociation("attributes"); - break; - - case USER_CONSENTS: - this.setDelegateWithAssociation("consents"); - break; - - case FEDERATED_IDENTITIES: - this.setDelegateWithAssociation("federatedIdentities"); - break; - - default: - setDelegate(em.find(JpaUserEntity.class, UUID.fromString(getDelegate().getId()))); - } - } else { - throw new IllegalStateException("Not a valid realm field: " + field); - } - } else { - setDelegate(em.find(JpaUserEntity.class, UUID.fromString(getDelegate().getId()))); - } - return getDelegate(); - } - - protected void setDelegateWithAssociation(final String associationName) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(JpaUserEntity.class); - Root root = query.from(JpaUserEntity.class); - root.fetch(associationName, JoinType.LEFT); - query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId()))); - setDelegate(em.createQuery(query).getSingleResult()); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserAttributeEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserAttributeEntity.java deleted file mode 100644 index 94ec72ea74e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserAttributeEntity.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.keycloak.models.map.storage.jpa.JpaAttributeEntityWithHashValue; - -/** - * JPA implementation for user attributes. This entity represents a user attribute and has a many-to-one relationship - * with the user entity. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_user_attribute", uniqueConstraints = { - @UniqueConstraint(columnNames = {"fk_root", "name", "value_hash"}) -}) -public class JpaUserAttributeEntity extends JpaAttributeEntityWithHashValue { - - public JpaUserAttributeEntity() { - } - - public JpaUserAttributeEntity(final JpaUserEntity root, final String name, final String value) { - super(root, name, value); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserConsentEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserConsentEntity.java deleted file mode 100644 index 705e6a77d8e..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserConsentEntity.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.user.MapUserConsentEntity; - -/** - * JPA {@link MapUserConsentEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_user_consent", - uniqueConstraints = { - @UniqueConstraint(columnNames = {"clientId"}) - }) -public class JpaUserConsentEntity extends UpdatableEntity.Impl implements MapUserConsentEntity, JpaRootEntity, JpaChildEntity { - - @Id - @Column - @GeneratedValue - private UUID id; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String clientId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaUserConsentMetadata metadata; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="fk_root") - private JpaUserEntity root; - - public JpaUserConsentEntity() { - this.metadata = new JpaUserConsentMetadata(); - } - - public JpaUserConsentEntity(final DeepCloner cloner) { - this.metadata = new JpaUserConsentMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return this.metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer version) { - this.metadata.setEntityVersion(version); - } - - @Override - public JpaUserEntity getParent() { - return this.root; - } - - public void setParent(final JpaUserEntity root) { - this.root = root; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - this.id = id == null ? null : UUID.fromString(id); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_USER_CONSENT; - } - - @Override - public String getClientId() { - if (isMetadataInitialized()) return this.metadata.getClientId(); - return clientId; - } - - @Override - public void setClientId(String clientId) { - this.metadata.setClientId(clientId); - } - - @Override - public Set getGrantedClientScopesIds() { - return this.metadata.getGrantedClientScopesIds(); - } - - @Override - public void addGrantedClientScopesId(String scope) { - this.metadata.addGrantedClientScopesId(scope); - } - - @Override - public void setGrantedClientScopesIds(Set scopesIds) { - this.metadata.setGrantedClientScopesIds(scopesIds); - } - - @Override - public void removeGrantedClientScopesId(String scopes) { - this.metadata.removeGrantedClientScopesId(scopes); - } - - @Override - public Long getCreatedDate() { - return this.metadata.getCreatedDate(); - } - - @Override - public void setCreatedDate(Long createdDate) { - this.metadata.setCreatedDate(createdDate); - } - - @Override - public Long getLastUpdatedDate() { - return this.metadata.getLastUpdatedDate(); - } - - @Override - public void setLastUpdatedDate(Long lastUpdatedDate) { - this.metadata.setLastUpdatedDate(lastUpdatedDate); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaUserConsentEntity)) return false; - return Objects.equals(id, ((JpaUserConsentEntity) obj).id); - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserConsentMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserConsentMetadata.java deleted file mode 100644 index e23ed17a33f..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserConsentMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.user.MapUserConsentEntityImpl; - -/** - * Class that contains all the user consent metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaUserConsentMetadata extends MapUserConsentEntityImpl implements Serializable { - - public JpaUserConsentMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - public JpaUserConsentMetadata(final DeepCloner cloner) { - super(cloner); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserEntity.java deleted file mode 100644 index 4d19f597722..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserEntity.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; - -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.user.MapUserConsentEntity; -import org.keycloak.models.map.user.MapUserCredentialEntity; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.user.MapUserFederatedIdentityEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Optional; -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER; - -/** - * JPA {@link MapUserEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_user", - uniqueConstraints = { - // if same username it can differ only in usernameWithCase - @UniqueConstraint(columnNames = {"realmId", "username", "usernameWithCase"}), - @UniqueConstraint(columnNames = {"realmId", "emailConstraint"}) - }) -@SuppressWarnings("ConstantConditions") -public class JpaUserEntity extends MapUserEntity.AbstractUserEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaUserMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String username; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String usernameWithCase; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String firstName; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String lastName; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String email; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String emailConstraint; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String federationLink; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Boolean enabled; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Boolean emailVerified; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long timestamp; - - @Column(name = "group_id") - @ElementCollection - @CollectionTable(name = "kc_user_group", joinColumns = @JoinColumn(name = "user_id", nullable = false)) - private final Set groupIds = new HashSet<>(); - - @Column(name = "role_id") - @ElementCollection - @CollectionTable(name = "kc_user_role", joinColumns = @JoinColumn(name = "user_id", nullable = false)) - private final Set roleIds = new HashSet<>(); - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set attributes = new HashSet<>(); - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set consents = new HashSet<>(); - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set federatedIdentities = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaUserEntity() { - this.metadata = new JpaUserMetadata(); - } - - public JpaUserEntity(final DeepCloner cloner) { - this.metadata = new JpaUserMetadata(cloner); - } - - /** - * Used by hibernate when calling cb.construct from read(QueryParameters) method. - * It is used to select user without metadata(json) field. - */ - public JpaUserEntity(final UUID id, final int version, final Integer entityVersion, final String realmId, final String username, - final String usernameWithCase, final String firstName, final String lastName, final String email, - final String emailConstraint, final String federationLink, final Boolean enabled, final Boolean emailVerified, - final Long timestamp) { - this.id = id; - this.version = version; - this.entityVersion = entityVersion; - this.realmId = realmId; - this.username = username; - this.usernameWithCase = usernameWithCase; - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - this.emailConstraint = emailConstraint; - this.federationLink = federationLink; - this.enabled = enabled; - this.emailVerified = emailVerified; - this.timestamp = timestamp; - this.metadata = null; - } - - public boolean isMetadataInitialized() { - return this.metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return this.metadata.getEntityVersion(); - return this.entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - this.metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_USER; - } - - @Override - public int getVersion() { - return this.version; - } - - @Override - public String getId() { - return this.id == null ? null : this.id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (this.isMetadataInitialized()) return this.metadata.getRealmId(); - return this.realmId; - } - - @Override - public void setRealmId(String realmId) { - this.metadata.setRealmId(realmId); - } - - /** - * @return User's username with respecting letter case. - */ - @Override - public String getUsername() { - if (this.isMetadataInitialized()) return this.metadata.getUsernameWithCase(); - //entities with entityVersion 1 the usernameWithCase might not be filled yet, therefore there is tha fallback to username field - return this.usernameWithCase == null ? this.username : this.usernameWithCase; - } - - @Override - public void setUsername(String username) { - this.metadata.setUsername(KeycloakModelUtils.toLowerCaseSafe(username)); - this.metadata.setUsernameWithCase(username); - } - - @Override - public String getFirstName() { - if (this.isMetadataInitialized()) return this.metadata.getFirstName(); - return this.firstName; - } - - @Override - public void setFirstName(String firstName) { - this.metadata.setFirstName(firstName); - } - - @Override - public Long getCreatedTimestamp() { - if (this.isMetadataInitialized()) return this.metadata.getCreatedTimestamp(); - return this.timestamp; - } - - @Override - public void setCreatedTimestamp(Long createdTimestamp) { - this.metadata.setCreatedTimestamp(createdTimestamp); - } - - @Override - public String getLastName() { - if (this.isMetadataInitialized()) return this.metadata.getLastName(); - return this.lastName; - } - - @Override - public void setLastName(String lastName) { - this.metadata.setLastName(lastName); - } - - @Override - public String getEmail() { - if (this.isMetadataInitialized()) return this.metadata.getEmail(); - return this.email; - } - - @Override - public void setEmail(String email) { - this.metadata.setEmail(email); - } - - @Override - public Boolean isEnabled() { - if (this.isMetadataInitialized()) return this.metadata.isEnabled(); - return this.enabled; - } - - @Override - public void setEnabled(Boolean enabled) { - this.metadata.setEnabled(enabled); - } - - @Override - public Boolean isEmailVerified() { - if (this.isMetadataInitialized()) return this.metadata.isEmailVerified(); - return this.emailVerified; - } - - @Override - public void setEmailVerified(Boolean emailVerified) { - this.metadata.setEmailVerified(emailVerified); - } - - @Override - public String getEmailConstraint() { - if (this.isMetadataInitialized()) return this.metadata.getEmailConstraint(); - return this.emailConstraint; - } - - @Override - public void setEmailConstraint(String emailConstraint) { - this.metadata.setEmailConstraint(emailConstraint); - } - - @Override - public String getFederationLink() { - if (this.isMetadataInitialized()) return this.metadata.getFederationLink(); - return this.federationLink; - } - - @Override - public void setFederationLink(String federationLink) { - this.metadata.setFederationLink(federationLink); - } - - @Override - public String getServiceAccountClientLink() { - return this.metadata.getServiceAccountClientLink(); - } - - @Override - public void setServiceAccountClientLink(String serviceAccountClientLink) { - this.metadata.setServiceAccountClientLink(serviceAccountClientLink); - } - - @Override - public Long getNotBefore() { - return this.metadata.getNotBefore(); - } - - @Override - public void setNotBefore(Long notBefore) { - this.metadata.setNotBefore(notBefore); - } - - //groups membership - @Override - public Set getGroupsMembership() { - return this.groupIds; - } - - @Override - public void setGroupsMembership(Set groupsMembership) { - this.groupIds.clear(); - if (groupsMembership != null) this.groupIds.addAll(groupsMembership); - } - - @Override - public void addGroupsMembership(String groupId) { - this.groupIds.add(groupId); - } - - @Override - public void removeGroupsMembership(String groupId) { - this.groupIds.remove(groupId); - } - - //roles membership - @Override - public Set getRolesMembership() { - return this.roleIds; - } - - @Override - public void setRolesMembership(Set rolesMembership) { - this.roleIds.clear(); - if (rolesMembership != null) this.roleIds.addAll(rolesMembership); - } - - @Override - public void addRolesMembership(String roleId) { - this.roleIds.add(roleId); - } - - @Override - public void removeRolesMembership(String roleId) { - this.roleIds.remove(roleId); - } - - //user required actions - @Override - public Set getRequiredActions() { - return this.metadata.getRequiredActions(); - } - - @Override - public void setRequiredActions(Set requiredActions) { - this.metadata.setRequiredActions(requiredActions); - } - - @Override - public void addRequiredAction(String requiredAction) { - this.metadata.addRequiredAction(requiredAction); - } - - @Override - public void removeRequiredAction(String requiredAction) { - this.metadata.removeRequiredAction(requiredAction); - } - - //user attributes - @Override - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (JpaUserAttributeEntity attribute : this.attributes) { - List values = result.getOrDefault(attribute.getName(), new LinkedList<>()); - values.add(attribute.getValue()); - result.put(attribute.getName(), values); - } - return result; - } - - @Override - public void setAttributes(Map> attributes) { - this.attributes.clear(); - if (attributes != null) { - attributes.forEach(this::setAttribute); - } - } - - @Override - public List getAttribute(String name) { - return this.attributes.stream().filter(a -> Objects.equals(a.getName(), name)) - .map(JpaUserAttributeEntity::getValue) - .collect(Collectors.toList()); - } - - @Override - public void setAttribute(String name, List values) { - this.removeAttribute(name); - if (values != null) { - values.forEach(value -> this.attributes.add(new JpaUserAttributeEntity(this, name, value))); - } - } - - @Override - public void removeAttribute(String name) { - this.attributes.removeIf(attr -> Objects.equals(attr.getName(), name)); - } - - //user consents - @Override - public Set getUserConsents() { - return this.consents.stream().map(MapUserConsentEntity.class::cast).collect(Collectors.toSet()); - } - - @Override - public Optional getUserConsent(String clientId) { - return this.consents.stream() - .map(MapUserConsentEntity.class::cast) - .filter(muce -> Objects.equals(muce.getClientId(), clientId)) - .findAny(); - } - - @Override - public void setUserConsents(Set userConsents) { - this.consents.clear(); - if (userConsents != null) { - userConsents.forEach(this::addUserConsent); - } - } - - @Override - public void addUserConsent(MapUserConsentEntity userConsentEntity) { - JpaUserConsentEntity entity = (JpaUserConsentEntity) CLONER.from(userConsentEntity); - entity.setParent(this); - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_USER_CONSENT); - this.consents.add(entity); - } - - @Override - public Boolean removeUserConsent(MapUserConsentEntity userConsentEntity) { - return removeUserConsent(userConsentEntity.getClientId()); - } - - @Override - public Boolean removeUserConsent(String clientId) { - return this.consents.removeIf(uc -> Objects.equals(uc.getClientId(), clientId)); - } - - //user credentials - @Override - public List getCredentials() { - return this.metadata.getCredentials(); - } - - @Override - public Optional getCredential(String id) { - return metadata.getCredential(id); - } - - @Override - public void setCredentials(List credentials) { - this.metadata.setCredentials(credentials); - } - - @Override - public void addCredential(MapUserCredentialEntity credentialEntity) { - this.metadata.addCredential(credentialEntity); - } - - @Override - public Boolean removeCredential(MapUserCredentialEntity credentialEntity) { - return removeCredential(credentialEntity.getId()); - } - - @Override - public Boolean removeCredential(String id) { - return metadata.removeCredential(id); - } - - //user federated identities - @Override - public Set getFederatedIdentities() { - return this.federatedIdentities.stream().map(MapUserFederatedIdentityEntity.class::cast).collect(Collectors.toSet()); - } - - @Override - public Optional getFederatedIdentity(String identityProviderId) { - return this.federatedIdentities.stream() - .map(MapUserFederatedIdentityEntity.class::cast) - .filter(muce -> Objects.equals(muce.getIdentityProvider(), identityProviderId)) - .findAny(); - } - - @Override - public void setFederatedIdentities(Set federatedIdentities) { - this.federatedIdentities.clear(); - if (federatedIdentities != null) { - federatedIdentities.forEach(this::addFederatedIdentity); - } - } - - @Override - public void addFederatedIdentity(MapUserFederatedIdentityEntity federatedIdentity) { - JpaUserFederatedIdentityEntity entity = (JpaUserFederatedIdentityEntity) CLONER.from(federatedIdentity); - entity.setParent(this); - entity.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY); - this.federatedIdentities.add(entity); - } - - @Override - public Boolean removeFederatedIdentity(MapUserFederatedIdentityEntity federatedIdentity) { - return removeFederatedIdentity(federatedIdentity.getIdentityProvider()); - } - - @Override - public Boolean removeFederatedIdentity(String identityProviderId) { - return this.federatedIdentities.removeIf(fi -> Objects.equals(fi.getIdentityProvider(), identityProviderId)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaUserEntity)) return false; - return Objects.equals(getId(), ((JpaUserEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserFederatedIdentityEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserFederatedIdentityEntity.java deleted file mode 100644 index 6315a72237a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserFederatedIdentityEntity.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import java.util.Objects; -import java.util.UUID; - -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.user.MapUserFederatedIdentityEntity; - -/** - * JPA {@link MapUserFederatedIdentityEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)} - * to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable. - * - * @author Stefan Guilhen - */ -@Entity -@Table(name = "kc_user_federated_identity") -public class JpaUserFederatedIdentityEntity extends UpdatableEntity.Impl implements MapUserFederatedIdentityEntity, JpaRootEntity, JpaChildEntity { - - @Id - @Column - @GeneratedValue - private UUID id; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String identityProvider; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String userId; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaUserFederatedIdentityMetadata metadata; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="fk_root") - private JpaUserEntity root; - - public JpaUserFederatedIdentityEntity() { - this.metadata = new JpaUserFederatedIdentityMetadata(); - } - - public JpaUserFederatedIdentityEntity(final DeepCloner cloner) { - this.metadata = new JpaUserFederatedIdentityMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return this.metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer version) { - this.metadata.setEntityVersion(version); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY; - } - - @Override - public JpaUserEntity getParent() { - return this.root; - } - - public void setParent(final JpaUserEntity root) { - this.root = root; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - this.id = id == null ? null : UUID.fromString(id); - } - - @Override - public String getToken() { - return this.metadata.getToken(); - } - - @Override - public void setToken(String token) { - this.metadata.setToken(token); - } - - @Override - public String getUserId() { - if (isMetadataInitialized()) return this.metadata.getUserId(); - return userId; - } - - @Override - public void setUserId(String userId) { - this.metadata.setUserId(userId); - } - - @Override - public String getIdentityProvider() { - if (isMetadataInitialized()) return this.metadata.getIdentityProvider(); - return identityProvider; - } - - @Override - public void setIdentityProvider(String identityProvider) { - this.metadata.setIdentityProvider(identityProvider); - } - - @Override - public String getUserName() { - return this.metadata.getUserName(); - } - - @Override - public void setUserName(String userName) { - this.metadata.setUserName(userName); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaUserFederatedIdentityEntity)) return false; - return Objects.equals(id, ((JpaUserFederatedIdentityEntity) obj).id); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserFederatedIdentityMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserFederatedIdentityMetadata.java deleted file mode 100644 index f5c13e484d6..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserFederatedIdentityMetadata.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.user.MapUserFederatedIdentityEntityImpl; - -/** - * Class that contains all the user federated identity metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaUserFederatedIdentityMetadata extends MapUserFederatedIdentityEntityImpl implements Serializable { - - public JpaUserFederatedIdentityMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - public JpaUserFederatedIdentityMetadata(final DeepCloner cloner) { - super(cloner); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserMetadata.java deleted file mode 100644 index ffadb654071..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/entity/JpaUserMetadata.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.user.entity; - -import java.io.Serializable; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.user.MapUserEntityImpl; - -/** - * Class that contains all the user metadata that is written as JSON into the database. - * - * @author Stefan Guilhen - */ -public class JpaUserMetadata extends MapUserEntityImpl implements Serializable { - - public JpaUserMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - public JpaUserMetadata(final DeepCloner cloner) { - super(cloner); - } - - private Integer entityVersion; - private String usernameWithCase; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - - public void setUsernameWithCase(String username) { - this.usernameWithCase = username; - } - - public String getUsernameWithCase() { - return this.usernameWithCase; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionMapStorage.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionMapStorage.java deleted file mode 100644 index 836d787369d..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionMapStorage.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.storage.jpa.JpaMapStorage; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaRootEntity; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity; - -import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_SESSION; - -public class JpaUserSessionMapStorage extends JpaMapStorage { - - public JpaUserSessionMapStorage(KeycloakSession session, final EntityManager em) { - super(session, JpaUserSessionEntity.class, UserSessionModel.class, em); - } - - @Override - protected Selection selectCbConstruct(CriteriaBuilder cb, Root root) { - return root; - } - - @Override - protected void setEntityVersion(JpaRootEntity entity) { - entity.setEntityVersion(CURRENT_SCHEMA_VERSION_USER_SESSION); - } - - @Override - protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() { - return new JpaUserSessionModelCriteriaBuilder(); - } - - @Override - protected MapUserSessionEntity mapToEntityDelegate(JpaUserSessionEntity original) { - return original; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionModelCriteriaBuilder.java deleted file mode 100644 index 10cf515cf14..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionModelCriteriaBuilder.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession; - -import java.util.Objects; -import java.util.UUID; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; - -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionModel.SearchableFields; -import org.keycloak.models.map.common.StringKeyConverter.UUIDKey; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; -import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity; -import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionNoteEntity; -import org.keycloak.storage.SearchableModelField; - -public class JpaUserSessionModelCriteriaBuilder extends JpaModelCriteriaBuilder { - - public JpaUserSessionModelCriteriaBuilder() { - super(JpaUserSessionModelCriteriaBuilder::new); - } - - private JpaUserSessionModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { - super(JpaUserSessionModelCriteriaBuilder::new, predicateFunc); - } - - @Override - public JpaUserSessionModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch(op) { - case EQ: - if (modelField == SearchableFields.ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> { - UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null)); - if (uuid == null) return cb.or(); - return cb.equal(root.get(modelField.getName()), uuid); - }); - } else if (modelField == SearchableFields.REALM_ID || - modelField == SearchableFields.USER_ID || - modelField == SearchableFields.BROKER_USER_ID || - modelField == SearchableFields.BROKER_SESSION_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get(modelField.getName()), value[0]) - ); - } else if (modelField == SearchableFields.IS_OFFLINE) { - - validateValue(value, modelField, op, Boolean.class); - - return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> - cb.equal(root.get("offline"), value[0]) - ); - } else if (modelField == SearchableFields.CORRESPONDING_SESSION_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> { - Join join = root.join("notes", JoinType.LEFT); - return cb.and( - cb.equal(join.get("name"), UserSessionModel.CORRESPONDING_SESSION_ID), - cb.equal(join.get("value"), value[0]) - ); - }); - } else if (modelField == SearchableFields.CLIENT_ID) { - - validateValue(value, modelField, op, String.class); - - return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> { - return cb.equal(root.join("clientSessions", JoinType.LEFT).get("clientId"), value[0]); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionEntity.java deleted file mode 100644 index c1974a45236..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionEntity.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession.entity; - -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaChildEntity; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity.AbstractAuthenticatedClientSessionEntity; - -/** - * Entity represents authenticated client session. - */ -@Entity -@Table(name = "kc_client_session") -public class JpaClientSessionEntity extends AbstractAuthenticatedClientSessionEntity implements JpaRootVersionedEntity, JpaChildEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaClientSessionMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String clientId; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set notes = new HashSet<>(); - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "fk_root") - private JpaUserSessionEntity root; - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaClientSessionEntity() { - this.metadata = new JpaClientSessionMetadata(); - } - - public JpaClientSessionEntity(DeepCloner cloner) { - this.metadata = new JpaClientSessionMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public JpaUserSessionEntity getParent() { - return root; - } - - public void setParent(JpaUserSessionEntity root) { - this.root = root; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_CLIENT_SESSION; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - return metadata.getRealmId(); - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getClientId() { - if (isMetadataInitialized()) return metadata.getClientId(); - return clientId; - } - - @Override - public void setClientId(String clientId) { - metadata.setClientId(clientId); - } - - @Override - public String getAuthMethod() { - return metadata.getAuthMethod(); - } - - @Override - public void setAuthMethod(String authMethod) { - metadata.setAuthMethod(authMethod); - } - - @Override - public String getRedirectUri() { - return metadata.getRedirectUri(); - } - - @Override - public void setRedirectUri(String redirectUri) { - metadata.setRedirectUri(redirectUri); - } - - @Override - public Long getTimestamp() { - return metadata.getTimestamp(); - } - - @Override - public void setTimestamp(Long timestamp) { - metadata.setTimestamp(timestamp); - } - - @Override - public Long getExpiration() { - return metadata.getExpiration(); - } - - @Override - public void setExpiration(Long expiration) { - metadata.setExpiration(expiration); - } - - @Override - public String getAction() { - return metadata.getAction(); - } - - @Override - public void setAction(String action) { - metadata.setAction(action); - } - - @Override - public String getCurrentRefreshToken() { - return metadata.getCurrentRefreshToken(); - } - - @Override - public void setCurrentRefreshToken(String currentRefreshToken) { - metadata.setCurrentRefreshToken(currentRefreshToken); - } - - @Override - public Integer getCurrentRefreshTokenUseCount() { - return metadata.getCurrentRefreshTokenUseCount(); - } - - @Override - public void setCurrentRefreshTokenUseCount(Integer currentRefreshTokenUseCount) { - metadata.setCurrentRefreshTokenUseCount(currentRefreshTokenUseCount); - } - - @Override - public Boolean isOffline() { - return metadata.isOffline(); - } - - @Override - public void setOffline(Boolean offline) { - metadata.setOffline(offline); - } - - @Override - public Map getNotes() { - return notes.stream().collect(Collectors.toMap(JpaClientSessionNoteEntity::getName, JpaClientSessionNoteEntity::getValue)); - } - - @Override - public void setNotes(Map notes) { - this.notes.clear(); - if (notes == null) return; - for (Map.Entry entry : notes.entrySet()) { - setNote(entry.getKey(), entry.getValue()); - } - } - - @Override - public String getNote(String name) { - return notes.stream() - .filter(obj -> Objects.equals(obj.getName(), name)) - .findFirst() - .map(JpaClientSessionNoteEntity::getValue) - .orElse(null); - } - - @Override - public Boolean removeNote(String name) { - return notes.removeIf(obj -> Objects.equals(obj.getName(), name)); - } - - @Override - public void setNote(String name, String value) { - removeNote(name); - if (name == null || value == null || value.trim().isEmpty()) return; - notes.add(new JpaClientSessionNoteEntity(this, name, value)); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaClientSessionEntity)) return false; - return Objects.equals(getId(), ((JpaClientSessionEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionMetadata.java deleted file mode 100644 index 4bcc03703fd..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession.entity; - -import java.io.Serializable; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityImpl; - -public class JpaClientSessionMetadata extends MapAuthenticatedClientSessionEntityImpl implements Serializable { - - public JpaClientSessionMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaClientSessionMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionNoteEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionNoteEntity.java deleted file mode 100644 index 2e69179c46b..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionNoteEntity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession.entity; - -import java.util.Objects; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import org.keycloak.models.map.storage.jpa.JpaAttributeEntity; - -@Entity -@Table(name = "kc_client_session_note") -public class JpaClientSessionNoteEntity extends JpaAttributeEntity { - - public JpaClientSessionNoteEntity() { - } - - public JpaClientSessionNoteEntity(JpaClientSessionEntity root, String name, String value) { - super(root, name, value); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaClientSessionNoteEntity)) return false; - JpaClientSessionNoteEntity that = (JpaClientSessionNoteEntity) obj; - return Objects.equals(getParent(), that.getParent()) && - Objects.equals(getName(), that.getName()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionEntity.java deleted file mode 100644 index 17f2e2586b9..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionEntity.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession.entity; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Version; -import org.hibernate.annotations.Type; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UuidValidator; -import org.keycloak.models.map.storage.jpa.Constants; -import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity; -import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity.AbstractUserSessionEntity; - -import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER; - -/** - * There are some fields marked by {@code @Column(insertable = false, updatable = false)}. - * Those fields are automatically generated by database from json field, - * therefore marked as non-insertable and non-updatable to instruct hibernate. - */ -@Entity -@Table(name = "kc_user_session") -public class JpaUserSessionEntity extends AbstractUserSessionEntity implements JpaRootVersionedEntity { - - @Id - @Column - private UUID id; - - //used for implicit optimistic locking - @Version - @Column - private int version; - - @Type(JsonbType.class) - @Column(columnDefinition = "jsonb") - private final JpaUserSessionMetadata metadata; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Integer entityVersion; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String realmId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String userId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String brokerSessionId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private String brokerUserId; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Boolean offline; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long lastSessionRefresh; - - @Column(insertable = false, updatable = false) - @Basic(fetch = FetchType.LAZY) - private Long expiration; - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set notes = new HashSet<>(); - - @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) - private final Set clientSessions = new HashSet<>(); - - /** - * No-argument constructor, used by hibernate to instantiate entities. - */ - public JpaUserSessionEntity() { - this.metadata = new JpaUserSessionMetadata(); - } - - public JpaUserSessionEntity(DeepCloner cloner) { - this.metadata = new JpaUserSessionMetadata(cloner); - } - - public boolean isMetadataInitialized() { - return metadata != null; - } - - @Override - public Integer getEntityVersion() { - if (isMetadataInitialized()) return metadata.getEntityVersion(); - return entityVersion; - } - - @Override - public void setEntityVersion(Integer entityVersion) { - metadata.setEntityVersion(entityVersion); - } - - @Override - public Integer getCurrentSchemaVersion() { - return Constants.CURRENT_SCHEMA_VERSION_USER_SESSION; - } - - @Override - public int getVersion() { - return version; - } - - @Override - public String getId() { - return id == null ? null : id.toString(); - } - - @Override - public void setId(String id) { - String validatedId = UuidValidator.validateAndConvert(id); - this.id = UUID.fromString(validatedId); - } - - @Override - public String getRealmId() { - if (isMetadataInitialized()) return metadata.getRealmId(); - return realmId; - } - - @Override - public void setRealmId(String realmId) { - metadata.setRealmId(realmId); - } - - @Override - public String getUserId() { - if (isMetadataInitialized()) return metadata.getUserId(); - return userId; - } - - @Override - public void setUserId(String userId) { - metadata.setUserId(userId); - } - - @Override - public String getLoginUsername() { - return metadata.getLoginUsername(); - } - - @Override - public void setLoginUsername(String loginUsername) { - metadata.setLoginUsername(loginUsername); - } - - @Override - public String getIpAddress() { - return metadata.getIpAddress(); - } - - @Override - public void setIpAddress(String ipAddress) { - metadata.setIpAddress(ipAddress); - } - - @Override - public String getAuthMethod() { - return metadata.getAuthMethod(); - } - - @Override - public void setAuthMethod(String authMethod) { - metadata.setAuthMethod(authMethod); - } - - @Override - public Boolean isOffline() { - if (isMetadataInitialized()) return metadata.isOffline(); - return offline; - } - - @Override - public void setOffline(Boolean offline) { - metadata.setOffline(offline); - } - - @Override - public Boolean isRememberMe() { - return metadata.isRememberMe(); - } - - @Override - public void setRememberMe(Boolean rememberMe) { - metadata.setRememberMe(rememberMe); - } - - @Override - public Long getTimestamp() { - return metadata.getTimestamp(); - } - - @Override - public void setTimestamp(Long timestamp) { - metadata.setTimestamp(timestamp); - } - - @Override - public Long getLastSessionRefresh() { - if (isMetadataInitialized()) return metadata.getLastSessionRefresh(); - return lastSessionRefresh; - } - - @Override - public void setLastSessionRefresh(Long lastSessionRefresh) { - metadata.setLastSessionRefresh(lastSessionRefresh); - - } - - @Override - public Long getExpiration() { - if (isMetadataInitialized()) return metadata.getExpiration(); - return expiration; - } - - @Override - public void setExpiration(Long expiration) { - metadata.setExpiration(expiration); - } - - @Override - public UserSessionModel.State getState() { - return metadata.getState(); - } - - @Override - public void setState(UserSessionModel.State state) { - metadata.setState(state); - } - - @Override - public UserSessionModel.SessionPersistenceState getPersistenceState() { - return UserSessionModel.SessionPersistenceState.PERSISTENT; - } - - @Override - public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) { - // no-op: each non-transient user session (stored in the db) has PERSISTENT state - } - - @Override - public Map getNotes() { - return Collections.unmodifiableMap(notes.stream().collect(Collectors.toMap(JpaUserSessionNoteEntity::getName, JpaUserSessionNoteEntity::getValue))); - } - - @Override - public String getNote(String name) { - return notes.stream() - .filter(obj -> Objects.equals(obj.getName(), name)) - .findFirst() - .map(JpaUserSessionNoteEntity::getValue) - .orElse(null); - } - - @Override - public void setNotes(Map notes) { - this.notes.clear(); - if (notes == null) return; - for (Map.Entry entry : notes.entrySet()) { - setNote(entry.getKey(), entry.getValue()); - } - } - - @Override - public Boolean removeNote(String name) { - return notes.removeIf(obj -> Objects.equals(obj.getName(), name)); - } - - @Override - public void setNote(String name, String value) { - removeNote(name); - if (name == null || value == null || value.trim().isEmpty()) return; - notes.add(new JpaUserSessionNoteEntity(this, name, value)); - } - - @Override - public String getBrokerSessionId() { - if (isMetadataInitialized()) return metadata.getBrokerSessionId(); - return brokerSessionId; - } - - @Override - public void setBrokerSessionId(String brokerSessionId) { - metadata.setBrokerSessionId(brokerSessionId); - } - - @Override - public String getBrokerUserId() { - if (isMetadataInitialized()) return metadata.getBrokerUserId(); - return brokerUserId; - } - - @Override - public void setBrokerUserId(String brokerUserId) { - metadata.setBrokerUserId(brokerUserId); - } - - @Override - public Set getAuthenticatedClientSessions() { - return clientSessions.stream().map(MapAuthenticatedClientSessionEntity.class::cast).collect(Collectors.toSet()); - } - - @Override - public void addAuthenticatedClientSession(MapAuthenticatedClientSessionEntity clientSession) { - JpaClientSessionEntity jpaClientSession = JpaClientSessionEntity.class.cast(CLONER.from(clientSession)); - jpaClientSession.setParent(this); - jpaClientSession.setEntityVersion(Constants.CURRENT_SCHEMA_VERSION_CLIENT_SESSION); - clientSessions.add(jpaClientSession); - } - - @Override - public Optional getAuthenticatedClientSession(String clientUUID) { - return clientSessions.stream().filter(cs -> Objects.equals(cs.getClientId(), clientUUID)).findFirst().map(MapAuthenticatedClientSessionEntity.class::cast); - } - - @Override - public Boolean removeAuthenticatedClientSession(String clientUUID) { - return clientSessions.removeIf(cs -> Objects.equals(cs.getClientId(), clientUUID)); - } - - @Override - public void clearAuthenticatedClientSessions() { - clientSessions.clear(); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaUserSessionEntity)) return false; - return Objects.equals(getId(), ((JpaUserSessionEntity) obj).getId()); - } -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionMetadata.java deleted file mode 100644 index ab71041e95a..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionMetadata.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession.entity; - -import java.io.Serializable; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.userSession.MapUserSessionEntityImpl; - -public class JpaUserSessionMetadata extends MapUserSessionEntityImpl implements Serializable { - - public JpaUserSessionMetadata(DeepCloner cloner) { - super(cloner); - } - - public JpaUserSessionMetadata() { - super(DeepCloner.DUMB_CLONER); - } - - private Integer entityVersion; - - public Integer getEntityVersion() { - return entityVersion; - } - - public void setEntityVersion(Integer entityVersion) { - this.entityVersion = entityVersion; - } - -} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionNoteEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionNoteEntity.java deleted file mode 100644 index a76eb4bb328..00000000000 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionNoteEntity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 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.storage.jpa.userSession.entity; - -import java.util.Objects; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import org.keycloak.models.map.storage.jpa.JpaAttributeEntity; - -@Entity -@Table(name = "kc_user_session_note") -public class JpaUserSessionNoteEntity extends JpaAttributeEntity { - - public JpaUserSessionNoteEntity() { - } - - public JpaUserSessionNoteEntity(JpaUserSessionEntity root, String name, String value) { - super(root, name, value); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof JpaUserSessionNoteEntity)) return false; - JpaUserSessionNoteEntity that = (JpaUserSessionNoteEntity) obj; - return Objects.equals(getParent(), that.getParent()) && - Objects.equals(getName(), that.getName()); - } -} diff --git a/model/map-jpa/src/main/resources/META-INF/auth-sessions/jpa-auth-sessions-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/auth-sessions/jpa-auth-sessions-changelog-1.xml deleted file mode 100644 index 7d51b83ef21..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/auth-sessions/jpa-auth-sessions-changelog-1.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/authz/permission/jpa-authz-permission-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/authz/permission/jpa-authz-permission-changelog-1.xml deleted file mode 100644 index aec88870065..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/authz/permission/jpa-authz-permission-changelog-1.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/authz/policy/jpa-authz-policy-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/authz/policy/jpa-authz-policy-changelog-1.xml deleted file mode 100644 index 52d9abccd07..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/authz/policy/jpa-authz-policy-changelog-1.xml +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/authz/resource-server/jpa-authz-resource-server-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/authz/resource-server/jpa-authz-resource-server-changelog-1.xml deleted file mode 100644 index 361db075305..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/authz/resource-server/jpa-authz-resource-server-changelog-1.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/authz/resource/jpa-authz-resource-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/authz/resource/jpa-authz-resource-changelog-1.xml deleted file mode 100644 index dc6c5d93c7f..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/authz/resource/jpa-authz-resource-changelog-1.xml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/authz/scope/jpa-authz-scope-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/authz/scope/jpa-authz-scope-changelog-1.xml deleted file mode 100644 index a4243062282..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/authz/scope/jpa-authz-scope-changelog-1.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/client-scopes/jpa-client-scopes-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/client-scopes/jpa-client-scopes-changelog-1.xml deleted file mode 100644 index 6953966c676..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/client-scopes/jpa-client-scopes-changelog-1.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/clients/jpa-clients-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/clients/jpa-clients-changelog-1.xml deleted file mode 100644 index eee2516aa99..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/clients/jpa-clients-changelog-1.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/events/admin-events/jpa-admin-events-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/events/admin-events/jpa-admin-events-changelog-1.xml deleted file mode 100644 index 445faec1cec..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/events/admin-events/jpa-admin-events-changelog-1.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/events/auth-events/jpa-auth-events-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/events/auth-events/jpa-auth-events-changelog-1.xml deleted file mode 100644 index 3c67562d308..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/events/auth-events/jpa-auth-events-changelog-1.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/groups/jpa-groups-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/groups/jpa-groups-changelog-1.xml deleted file mode 100644 index 502097de1ce..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/groups/jpa-groups-changelog-1.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml deleted file mode 100644 index 101370d0442..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-auth-sessions-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-auth-sessions-changelog.xml deleted file mode 100644 index 9f9504601b8..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-auth-sessions-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-authz-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-authz-changelog.xml deleted file mode 100644 index 15b47affa4c..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-authz-changelog.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-client-scopes-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-client-scopes-changelog.xml deleted file mode 100644 index ba6743da998..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-client-scopes-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-clients-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-clients-changelog.xml deleted file mode 100644 index 2d20cf39970..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-clients-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-events-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-events-changelog.xml deleted file mode 100644 index dad8fddc3df..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-events-changelog.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-groups-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-groups-changelog.xml deleted file mode 100644 index a6a7c64c47c..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-groups-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-locks-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-locks-changelog.xml deleted file mode 100644 index 93384ed18b2..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-locks-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-map/queries-default.properties b/model/map-jpa/src/main/resources/META-INF/jpa-map/queries-default.properties deleted file mode 100644 index f48481061a0..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-map/queries-default.properties +++ /dev/null @@ -1,6 +0,0 @@ -# properties file to define all default queries that are loaded separately -# in a properties file. These queries can be overloaded with a -# specific file for each database type. Queries are defined in the form: -# name[type]=sql -# type can be native (for native queries) or jpql (jpql syntax) -# if no type is defined jpql is the default diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-realms-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-realms-changelog.xml deleted file mode 100644 index 45d2c8364a0..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-realms-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-roles-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-roles-changelog.xml deleted file mode 100644 index 6ef426fd483..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-roles-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-single-use-objects-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-single-use-objects-changelog.xml deleted file mode 100644 index 4370ef6d94c..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-single-use-objects-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-user-login-failures-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-user-login-failures-changelog.xml deleted file mode 100644 index 5a2b5dc96a8..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-user-login-failures-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-user-sessions-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-user-sessions-changelog.xml deleted file mode 100644 index 14140aef26c..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-user-sessions-changelog.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-users-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-users-changelog.xml deleted file mode 100644 index 10a975cf2ab..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/jpa-users-changelog.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/locks/jpa-locks-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/locks/jpa-locks-changelog-1.xml deleted file mode 100644 index 12d5921e819..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/locks/jpa-locks-changelog-1.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/realms/jpa-realms-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/realms/jpa-realms-changelog-1.xml deleted file mode 100644 index d5f23fbafaf..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/realms/jpa-realms-changelog-1.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/roles/jpa-roles-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/roles/jpa-roles-changelog-1.xml deleted file mode 100644 index 2c7cc5edd16..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/roles/jpa-roles-changelog-1.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/services/liquibase.change.Change b/model/map-jpa/src/main/resources/META-INF/services/liquibase.change.Change deleted file mode 100644 index c4cc8ec7aef..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/liquibase.change.Change +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.liquibase.extension.CreateJsonIndexChange -org.keycloak.models.map.storage.jpa.liquibase.extension.GeneratedColumnChange \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/services/liquibase.datatype.LiquibaseDataType b/model/map-jpa/src/main/resources/META-INF/services/liquibase.datatype.LiquibaseDataType deleted file mode 100644 index d31575e4990..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/liquibase.datatype.LiquibaseDataType +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.liquibase.extension.JsonDataType -org.keycloak.models.map.storage.jpa.liquibase.extension.KeycloakHashDataType -org.keycloak.models.map.storage.jpa.liquibase.extension.KeycloakKeyDataType diff --git a/model/map-jpa/src/main/resources/META-INF/services/liquibase.lockservice.LockService b/model/map-jpa/src/main/resources/META-INF/services/liquibase.lockservice.LockService deleted file mode 100644 index 1795f500c35..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/liquibase.lockservice.LockService +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright 2023 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. -# - -# This is only used when running via Undertow and using the DefaultLiquibaseConnectionProvider. -# When using Quarkus, the class is explicitly named inside the LiquibaseProcessor. -org.keycloak.models.map.storage.jpa.liquibase.lockservice.KeycloakLockService diff --git a/model/map-jpa/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator b/model/map-jpa/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator deleted file mode 100644 index b882dd0a750..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.liquibase.extension.CreateJsonIndexGenerator -org.keycloak.models.map.storage.jpa.liquibase.extension.GeneratedColumnSqlGenerator \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor b/model/map-jpa/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor deleted file mode 100644 index 98057f67f18..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2023 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.hibernate.contributor.JpaMapFunctionContributor diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.hibernate.integrator.spi.Integrator b/model/map-jpa/src/main/resources/META-INF/services/org.hibernate.integrator.spi.Integrator deleted file mode 100644 index d3f84ab5883..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.hibernate.integrator.spi.Integrator +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.EventListenerIntegrator diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory b/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory deleted file mode 100644 index 037168bcf81..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionProviderFactory b/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionProviderFactory deleted file mode 100644 index bcc33e56dd9..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2016 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.liquibase.connection.DefaultLiquibaseConnectionProviderFactory \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProviderFactory b/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProviderFactory deleted file mode 100644 index 52e20e48d3e..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.liquibase.updater.MapJpaLiquibaseUpdaterProviderFactory diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.provider.ExceptionConverter b/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.provider.ExceptionConverter deleted file mode 100644 index 57d9e95bbca..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.provider.ExceptionConverter +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.models.map.storage.jpa.hibernate.JpaMapExceptionConverter \ No newline at end of file diff --git a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index 02e1ec37405..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterSpi -org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionSpi diff --git a/model/map-jpa/src/main/resources/META-INF/single-use-objects/jpa-single-use-objects-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/single-use-objects/jpa-single-use-objects-changelog-1.xml deleted file mode 100644 index 904e2eb29e0..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/single-use-objects/jpa-single-use-objects-changelog-1.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/user-login-failures/jpa-user-login-failures-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/user-login-failures/jpa-user-login-failures-changelog-1.xml deleted file mode 100644 index e103e4c3ecf..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/user-login-failures/jpa-user-login-failures-changelog-1.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/user-sessions/jpa-user-sessions-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/user-sessions/jpa-user-sessions-changelog-1.xml deleted file mode 100644 index 506ec391be6..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/user-sessions/jpa-user-sessions-changelog-1.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/users/jpa-users-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/users/jpa-users-changelog-1.xml deleted file mode 100644 index 5d580c2840a..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/users/jpa-users-changelog-1.xml +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/META-INF/users/jpa-users-changelog-2.xml b/model/map-jpa/src/main/resources/META-INF/users/jpa-users-changelog-2.xml deleted file mode 100644 index 2898f300073..00000000000 --- a/model/map-jpa/src/main/resources/META-INF/users/jpa-users-changelog-2.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model/map-jpa/src/main/resources/default-map-jpa-persistence.xml b/model/map-jpa/src/main/resources/default-map-jpa-persistence.xml deleted file mode 100644 index 14d9bdd5cfe..00000000000 --- a/model/map-jpa/src/main/resources/default-map-jpa-persistence.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity - org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionEntity - - org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerEntity - org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity - org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceAttributeEntity - org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEntity - org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity - org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity - org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyConfigEntity - - org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeEntity - org.keycloak.models.map.storage.jpa.clientScope.entity.JpaClientScopeAttributeEntity - - org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity - org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity - - org.keycloak.models.map.storage.jpa.event.admin.entity.JpaAdminEventEntity - org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventEntity - org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventDetailEntity - - org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity - org.keycloak.models.map.storage.jpa.group.entity.JpaGroupAttributeEntity - - org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity - org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity - org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmAttributeEntity - - org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity - org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntity - org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity - - org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity - org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectNoteEntity - - org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity - - org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionEntity - org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionNoteEntity - org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity - org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionNoteEntity - - org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity - org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity - org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity - org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityEntity - - org.keycloak.models.map.storage.jpa.lock.entity.JpaLockEntity - - diff --git a/model/map-ldap/pom.xml b/model/map-ldap/pom.xml deleted file mode 100644 index 6a70680c034..00000000000 --- a/model/map-ldap/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - keycloak-model-pom - org.keycloak - 999.0.0-SNAPSHOT - - 4.0.0 - - keycloak-model-map-ldap - Keycloak Model Map LDAP - - - - org.keycloak - keycloak-model-map - ${project.version} - - - org.keycloak - keycloak-server-spi-private - - - commons-lang - commons-lang - - - junit - junit - test - - - diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorage.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorage.java deleted file mode 100644 index b70ebb6d187..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorage.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.stream.Collectors; - -import org.keycloak.models.KeycloakTransaction; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.QueryParameters; - -public abstract class LdapMapStorage implements MapStorage, KeycloakTransaction { - - private boolean active; - private boolean rollback; - - public LdapMapStorage() { - } - - protected abstract static class MapTaskWithValue { - public abstract void execute(); - } - - protected abstract static class DeleteOperation extends MapTaskWithValue { - } - - protected final LinkedList tasksOnRollback = new LinkedList<>(); - - protected final LinkedList tasksOnCommit = new LinkedList<>(); - - protected final Map entities = new HashMap<>(); - - public long getCount(QueryParameters queryParameters) { - return read(queryParameters).count(); - } - - public long delete(QueryParameters queryParameters) { - return read(queryParameters).map(m -> delete(m.getId()) ? 1 : 0).collect(Collectors.summarizingLong(val -> val)).getSum(); - } - - @Override - public void begin() { - active = true; - } - - @Override - public void commit() { - if (rollback) { - throw new RuntimeException("Rollback only!"); - } - } - - @Override - public void rollback() { - } - - @Override - public void setRollbackOnly() { - rollback = true; - } - - @Override - public boolean getRollbackOnly() { - return rollback; - } - - @Override - public boolean isActive() { - return active; - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorageProvider.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorageProvider.java deleted file mode 100644 index 72669c117a2..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorageProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag; - -public class LdapMapStorageProvider implements MapStorageProvider { - - private final KeycloakSession session; - private final LdapMapStorageProviderFactory factory; - private final int factoryId; - - public LdapMapStorageProvider(KeycloakSession session, LdapMapStorageProviderFactory factory, int factoryId) { - this.session = session; - this.factory = factory; - this.factoryId = factoryId; - } - - @Override - public void close() { - } - - @Override - public MapStorage getMapStorage(Class modelType, Flag... flags) { - return SessionAttributesUtils.createMapStorageIfAbsent(session, getClass(), modelType, factoryId, () -> { - LdapMapStorage store = (LdapMapStorage) factory.createMapStorage(session, modelType); - session.getTransactionManager().enlist(store); - return store; - }); - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorageProviderFactory.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorageProviderFactory.java deleted file mode 100644 index 9979d4d5066..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapMapStorageProviderFactory.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap; - -import java.util.HashMap; -import java.util.Map; - -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; -import org.keycloak.models.map.storage.ldap.role.LdapRoleMapStorage; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -public class LdapMapStorageProviderFactory implements - AmphibianProviderFactory, - MapStorageProviderFactory, - EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "ldap-map-storage"; - private final int factoryId = SessionAttributesUtils.grabNewFactoryIdentifier(); - - private Config.Scope config; - - @SuppressWarnings("rawtypes") - private static final Map, LdapRoleMapStorage.LdapRoleMapKeycloakTransactionFunction> MODEL_TO_STORE = new HashMap<>(); - static { - MODEL_TO_STORE.put(RoleModel.class, LdapRoleMapStorage::new); - } - - public MapStorage createMapStorage(KeycloakSession session, Class modelType) { - return MODEL_TO_STORE.get(modelType).apply(session, config); - } - - @Override - public MapStorageProvider create(KeycloakSession session) { - return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, LdapMapStorageProvider.class, session1 -> new LdapMapStorageProvider(session1, this, factoryId)); - } - - @Override - public void init(Config.Scope config) { - this.config = config; - LdapMapConfig cfg = new LdapMapConfig(config); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", cfg.getConnectionPoolingAuthentication(), "none simple"); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", cfg.getConnectionPoolingInitSize(), "1"); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", cfg.getConnectionPoolingMaxSize(), "1000"); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", cfg.getConnectionPoolingPrefSize(), "5"); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", cfg.getConnectionPoolingTimeout(), "300000"); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", cfg.getConnectionPoolingProtocol(), "plain ssl"); - checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", cfg.getConnectionPoolingDebug(), "off"); - } - - private static void checkSystemProperty(String name, String cfgValue, String defaultValue) { - String value = System.getProperty(name); - if(cfgValue != null) { - value = cfgValue; - } - if(value == null) { - value = defaultValue; - } - System.setProperty(name, value); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public String getHelpText() { - return "LDAP Map Storage"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public void close() { - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapModelCriteriaBuilder.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapModelCriteriaBuilder.java deleted file mode 100644 index 3750d393c2e..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/LdapModelCriteriaBuilder.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap; - -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.ldap.store.LdapMapUtil; -import org.keycloak.models.map.storage.ldap.store.LdapMapEscapeStrategy; -import org.keycloak.models.map.storage.ldap.store.LdapMapOctetStringEncoder; - -import java.util.Date; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * Abstract class containing methods common to all Ldap*ModelCriteriaBuilder implementations - * - * @param Entity - * @param Model - * @param specific implementation of this class - */ -public abstract class LdapModelCriteriaBuilder> implements ModelCriteriaBuilder { - - private final Function, Self> instantiator; - private Supplier predicateFunc = null; - - public LdapModelCriteriaBuilder(Function, Self> instantiator) { - this.instantiator = instantiator; - } - - @SuppressWarnings("unchecked") - @Override - public Self and(Self... builders) { - return instantiator.apply(() -> { - StringBuilder filter = new StringBuilder(); - for (Self builder : builders) { - filter.append(builder.getPredicateFunc().get()); - } - if (filter.length() > 0) { - filter.insert(0, "(&"); - filter.append(")"); - } - return filter; - }); - } - - @SuppressWarnings("unchecked") - @Override - public Self or(Self... builders) { - return instantiator.apply(() -> { - StringBuilder filter = new StringBuilder(); - filter.append("(|"); - for (Self builder : builders) { - filter.append(builder.getPredicateFunc().get()); - } - filter.append(")"); - return filter; - }); - } - - @Override - public Self not(Self builder) { - return instantiator.apply(() -> { - StringBuilder filter = new StringBuilder(); - filter.append("(!"); - filter.append(builder.getPredicateFunc().get()); - filter.append(")"); - return filter; - }); - } - - public Supplier getPredicateFunc() { - return predicateFunc; - } - - public LdapModelCriteriaBuilder(Function, Self> instantiator, - Supplier predicateFunc) { - this.instantiator = instantiator; - this.predicateFunc = predicateFunc; - } - - protected StringBuilder equal(String field, Object value, LdapMapEscapeStrategy ldapMapEscapeStrategy, boolean isBinary) { - Object parameterValue = value; - if (value instanceof Date) { - parameterValue = LdapMapUtil.formatDate((Date) parameterValue); - } - - String escaped = new LdapMapOctetStringEncoder(ldapMapEscapeStrategy).encode(parameterValue, isBinary); - - return new StringBuilder().append("(").append(field).append(LDAPConstants.EQUAL).append(escaped).append(")"); - } - - protected StringBuilder in(String name, Object[] valuesToCompare, boolean isBinary) { - StringBuilder filter = new StringBuilder(); - filter.append("(|("); - - for (Object o : valuesToCompare) { - Object value = new LdapMapOctetStringEncoder().encode(o, false); - - filter.append("(").append(name).append(LDAPConstants.EQUAL).append(value).append(")"); - } - - filter.append("))"); - return filter; - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/MapModelCriteriaBuilderAssumingEqualForField.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/MapModelCriteriaBuilderAssumingEqualForField.java deleted file mode 100644 index 580abf1cdb6..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/MapModelCriteriaBuilderAssumingEqualForField.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.storage.SearchableModelField; - -import java.util.Map; -import java.util.function.Predicate; - -public class MapModelCriteriaBuilderAssumingEqualForField extends MapModelCriteriaBuilder { - - private final Map, UpdatePredicatesFunc> fieldPredicates; - private final StringKeyConverter keyConverter; - private final SearchableModelField modelFieldThatShouldCompareToTrueForEqual; - - public MapModelCriteriaBuilderAssumingEqualForField(StringKeyConverter keyConverter, Map, UpdatePredicatesFunc> fieldPredicates, SearchableModelField modelFieldThatShouldCompareToTrueForEqual) { - this(keyConverter, fieldPredicates, ALWAYS_TRUE, ALWAYS_TRUE, modelFieldThatShouldCompareToTrueForEqual); - } - - protected MapModelCriteriaBuilderAssumingEqualForField(StringKeyConverter keyConverter, Map, UpdatePredicatesFunc> fieldPredicates, Predicate indexReadFilter, Predicate sequentialReadFilter, SearchableModelField modelFieldThatShouldCompareToTrueForEqual) { - super(keyConverter, fieldPredicates, indexReadFilter, sequentialReadFilter); - this.keyConverter = keyConverter; - this.modelFieldThatShouldCompareToTrueForEqual = modelFieldThatShouldCompareToTrueForEqual; - this.fieldPredicates = fieldPredicates; - } - - @Override - public MapModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... values) { - if (modelField == modelFieldThatShouldCompareToTrueForEqual && op == Operator.EQ) { - return instantiateNewInstance( - keyConverter, - fieldPredicates, - ALWAYS_TRUE, - ALWAYS_TRUE); - } - return super.compare(modelField, op, values); - } - - @Override - protected MapModelCriteriaBuilder instantiateNewInstance(StringKeyConverter keyConverter, Map, UpdatePredicatesFunc> fieldPredicates, Predicate indexReadFilter, Predicate sequentialReadFilter) { - return new MapModelCriteriaBuilderAssumingEqualForField<>(keyConverter, fieldPredicates, indexReadFilter, sequentialReadFilter, modelFieldThatShouldCompareToTrueForEqual); - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/config/LdapMapCommonGroupMapperConfig.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/config/LdapMapCommonGroupMapperConfig.java deleted file mode 100644 index 2b96a37c3ac..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/config/LdapMapCommonGroupMapperConfig.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2016 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.storage.ldap.config; - -import org.keycloak.component.ComponentModel; -import org.keycloak.models.LDAPConstants; - -import java.util.HashSet; -import java.util.Set; - -/** - * @author Marek Posolda - */ -public abstract class LdapMapCommonGroupMapperConfig { - - // Name of LDAP attribute on role, which is used for membership mappings. Usually it will be "member" - public static final String MEMBERSHIP_LDAP_ATTRIBUTE = "membership.ldap.attribute"; - - // See docs for MembershipType enum - public static final String MEMBERSHIP_ATTRIBUTE_TYPE = "membership.attribute.type"; - - // Used just for membershipType=UID. Name of LDAP attribute on user, which is used for membership mappings. Usually it will be "uid" - public static final String MEMBERSHIP_USER_LDAP_ATTRIBUTE = "membership.user.ldap.attribute"; - - // See docs for Mode enum - public static final String MODE = "mode"; - - // See docs for UserRolesRetrieveStrategy enum - public static final String USER_ROLES_RETRIEVE_STRATEGY = "user.roles.retrieve.strategy"; - - // Used just for UserRolesRetrieveStrategy.GetRolesFromUserMemberOfAttribute. It's the name of the attribute on LDAP user, which is used to track the groups which user is member. - // Usually it will "memberof" - public static final String MEMBEROF_LDAP_ATTRIBUTE = "memberof.ldap.attribute"; - - - protected final ComponentModel mapperModel; - - public LdapMapCommonGroupMapperConfig(ComponentModel mapperModel) { - this.mapperModel = mapperModel; - } - - public String getMembershipLdapAttribute() { - String membershipAttrName = mapperModel.getConfig().getFirst(MEMBERSHIP_LDAP_ATTRIBUTE); - return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER; - } - - public String getMembershipUserLdapAttribute(LdapMapConfig ldapMapConfig) { - String membershipUserAttrName = mapperModel.getConfig().getFirst(MEMBERSHIP_USER_LDAP_ATTRIBUTE); - return membershipUserAttrName!=null ? membershipUserAttrName : ldapMapConfig.getUsernameLdapAttribute(); - } - - public String getMemberOfLdapAttribute() { - String memberOfLdapAttrName = mapperModel.getConfig().getFirst(MEMBEROF_LDAP_ATTRIBUTE); - return memberOfLdapAttrName!=null ? memberOfLdapAttrName : LDAPConstants.MEMBER_OF; - } - - protected Set getConfigValues(String str) { - String[] objClasses = str.split(","); - Set trimmed = new HashSet<>(); - for (String objectClass : objClasses) { - objectClass = objectClass.trim(); - if (objectClass.length() > 0) { - trimmed.add(objectClass); - } - } - return trimmed; - } - - public abstract String getLDAPGroupNameLdapAttribute(); - - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/config/LdapMapConfig.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/config/LdapMapConfig.java deleted file mode 100644 index 4490981172a..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/config/LdapMapConfig.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.config; - -import org.keycloak.Config; -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.map.storage.ldap.role.config.LdapMapRoleMapperConfig; - -import javax.naming.directory.SearchControls; -import java.util.Collection; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; - -public class LdapMapConfig { - private final MultivaluedHashMap config; - - public LdapMapConfig(Config.Scope config) { - this.config = hm(config); - } - - private static MultivaluedHashMap hm(Config.Scope config) { - return new MultivaluedHashMap() { - @Override - public String getFirst(String key) { - return config.get(key); - } - }; - } - - // from: RoleMapperConfig - public Collection getRoleObjectClasses() { - String objectClasses = config.getFirst(LdapMapRoleMapperConfig.ROLE_OBJECT_CLASSES); - if (objectClasses == null) { - // For Active directory, the default is 'group' . For other servers 'groupOfNames' - objectClasses = isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; - } - - return getConfigValues(objectClasses); - } - - // from: RoleMapperConfig - protected Set getConfigValues(String str) { - String[] objClasses = str.split(","); - Set trimmed = new HashSet<>(); - for (String objectClass : objClasses) { - objectClass = objectClass.trim(); - if (objectClass.length() > 0) { - trimmed.add(objectClass); - } - } - return trimmed; - } - - private final Set binaryAttributeNames = new HashSet<>(); - - public String getConnectionUrl() { - return config.getFirst(LDAPConstants.CONNECTION_URL); - } - - public String getFactoryName() { - // hardcoded for now - return "com.sun.jndi.ldap.LdapCtxFactory"; - } - - public String getAuthType() { - String value = config.getFirst(LDAPConstants.AUTH_TYPE); - if (value == null) { - return LDAPConstants.AUTH_TYPE_SIMPLE; - } else { - return value; - } - } - - public boolean useExtendedPasswordModifyOp() { - String value = config.getFirst(LDAPConstants.USE_PASSWORD_MODIFY_EXTENDED_OP); - return Boolean.parseBoolean(value); - } - - public String getUseTruststoreSpi() { - return config.getFirst(LDAPConstants.USE_TRUSTSTORE_SPI); - } - - public String getUsersDn() { - String usersDn = config.getFirst(LDAPConstants.USERS_DN); - - if (usersDn == null) { - // Just for the backwards compatibility 1.2 -> 1.3 . Should be removed later. - usersDn = config.getFirst("userDnSuffix"); - } - - return usersDn; - } - - public Collection getUserObjectClasses() { - String objClassesCfg = config.getFirst(LDAPConstants.USER_OBJECT_CLASSES); - String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson,organizationalPerson"; - - String[] objectClasses = objClassesStr.split(","); - - // Trim them - Set userObjClasses = new HashSet<>(); - for (String objectClass : objectClasses) { - userObjClasses.add(objectClass.trim()); - } - return userObjClasses; - } - - public String getBindDN() { - return config.getFirst(LDAPConstants.BIND_DN); - } - - public String getBindCredential() { - return config.getFirst(LDAPConstants.BIND_CREDENTIAL); - } - - public String getVendor() { - return config.getFirst(LDAPConstants.VENDOR); - } - - public boolean isActiveDirectory() { - String vendor = getVendor(); - return vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY); - } - - public boolean isValidatePasswordPolicy() { - String validatePPolicy = config.getFirst(LDAPConstants.VALIDATE_PASSWORD_POLICY); - return Boolean.parseBoolean(validatePPolicy); - } - - public boolean isTrustEmail(){ - String trustEmail = config.getFirst(LDAPConstants.TRUST_EMAIL); - return Boolean.parseBoolean(trustEmail); - } - - public String getConnectionPooling() { - if(isStartTls()) { - return null; - } else { - return config.getFirst(LDAPConstants.CONNECTION_POOLING); - } - } - - public String getConnectionPoolingAuthentication() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_AUTHENTICATION); - } - - public String getConnectionPoolingDebug() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_DEBUG); - } - - public String getConnectionPoolingInitSize() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_INITSIZE); - } - - public String getConnectionPoolingMaxSize() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_MAXSIZE); - } - - public String getConnectionPoolingPrefSize() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_PREFSIZE); - } - - public String getConnectionPoolingProtocol() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_PROTOCOL); - } - - public String getConnectionPoolingTimeout() { - return config.getFirst(LDAPConstants.CONNECTION_POOLING_TIMEOUT); - } - - public String getConnectionTimeout() { - return config.getFirst(LDAPConstants.CONNECTION_TIMEOUT); - } - - public String getReadTimeout() { - return config.getFirst(LDAPConstants.READ_TIMEOUT); - } - - public Properties getAdditionalConnectionProperties() { - // not supported for now - return null; - } - - public int getSearchScope() { - String searchScope = config.getFirst(LDAPConstants.SEARCH_SCOPE); - return searchScope == null ? SearchControls.SUBTREE_SCOPE : Integer.parseInt(searchScope); - } - - public String getUuidLDAPAttributeName() { - String uuidAttrName = config.getFirst(LDAPConstants.UUID_LDAP_ATTRIBUTE); - if (uuidAttrName == null) { - // Differences of unique attribute among various vendors - String vendor = getVendor(); - uuidAttrName = LDAPConstants.getUuidAttributeName(vendor); - } - - return uuidAttrName; - } - - public boolean isObjectGUID() { - return getUuidLDAPAttributeName().equalsIgnoreCase(LDAPConstants.OBJECT_GUID); - } - - public boolean isEdirectoryGUID() { - return isEdirectory() && getUuidLDAPAttributeName().equalsIgnoreCase(LDAPConstants.NOVELL_EDIRECTORY_GUID); - } - - public boolean isPagination() { - String pagination = config.getFirst(LDAPConstants.PAGINATION); - return Boolean.parseBoolean(pagination); - } - - public int getBatchSizeForSync() { - String pageSizeConfig = config.getFirst(LDAPConstants.BATCH_SIZE_FOR_SYNC); - return pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC; - } - - public String getUsernameLdapAttribute() { - String username = config.getFirst(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); - if (username == null) { - username = isActiveDirectory() ? LDAPConstants.CN : LDAPConstants.UID; - } - return username; - } - - public String getRdnLdapAttribute() { - String rdn = config.getFirst(LDAPConstants.RDN_LDAP_ATTRIBUTE); - if (rdn == null) { - rdn = getUsernameLdapAttribute(); - - if (rdn.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME)) { - // Just for the backwards compatibility 1.2 -> 1.3 . Should be removed later. - rdn = LDAPConstants.CN; - } - - } - return rdn; - } - - - public String getCustomUserSearchFilter() { - String customFilter = config.getFirst(LDAPConstants.CUSTOM_USER_SEARCH_FILTER); - if (customFilter != null) { - customFilter = customFilter.trim(); - if (customFilter.length() > 0) { - return customFilter; - } - } - return null; - } - - public boolean isStartTls() { - return Boolean.parseBoolean(config.getFirst(LDAPConstants.START_TLS)); - } - - public void addBinaryAttribute(String attrName) { - binaryAttributeNames.add(attrName); - } - - public Set getBinaryAttributeNames() { - return binaryAttributeNames; - } - - - public boolean isEdirectory() { - return LDAPConstants.VENDOR_NOVELL_EDIRECTORY.equalsIgnoreCase(getVendor()); - } - - @Override - public String toString() { - MultivaluedHashMap copy = new MultivaluedHashMap<>(config); - copy.remove(LDAPConstants.BIND_CREDENTIAL); - return copy + ", binaryAttributes: " + binaryAttributeNames; - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapDn.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapDn.java deleted file mode 100644 index d5cfe0f3d31..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapDn.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2016 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.storage.ldap.model; - -import javax.naming.ldap.Rdn; -import java.util.Collection; -import java.util.Deque; -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * @author Marek Posolda - */ -public class LdapMapDn { - - private static final Pattern DN_PATTERN = Pattern.compile("(? entries; - - private LdapMapDn() { - this.entries = new LinkedList<>(); - } - - private LdapMapDn(Deque entries) { - this.entries = entries; - } - - public static LdapMapDn fromString(String dnString) { - LdapMapDn dn = new LdapMapDn(); - - // In certain OpenLDAP implementations the uniqueMember attribute is mandatory - // Thus, if a new group is created, it will contain an empty uniqueMember attribute - // Later on, when adding members, this empty attribute will be kept - // Keycloak must be able to process it, properly, w/o throwing an ArrayIndexOutOfBoundsException - if(dnString.trim().isEmpty()) - return dn; - - String[] rdns = DN_PATTERN.split(dnString); - for (String entryStr : rdns) { - if (entryStr.indexOf('+') == -1) { - // This is 99.9% of cases where RDN consists of single key-value pair - SubEntry subEntry = parseSingleSubEntry(dn, entryStr); - dn.addLast(new RDN(subEntry)); - } else { - // This is 0.1% of cases where RDN consists of more key-value pairs like "uid=foo+cn=bar" - String[] subEntries = ENTRY_PATTERN.split(entryStr); - RDN entry = new RDN(); - for (String subEntryStr : subEntries) { - SubEntry subEntry = parseSingleSubEntry(dn, subEntryStr); - entry.addSubEntry(subEntry); - } - dn.addLast(entry); - } - } - - return dn; - } - - // parse single sub-entry and add it to the "dn" . Assumption is that subentry is something like "uid=bar" and does not contain + character - private static SubEntry parseSingleSubEntry(LdapMapDn dn, String subEntryStr) { - String[] rdn = SUB_ENTRY_PATTERN.split(subEntryStr); - if (rdn.length >1) { - return new SubEntry(rdn[0].trim(), rdn[1].trim()); - } else { - return new SubEntry(rdn[0].trim(), ""); - } - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof LdapMapDn)) { - return false; - } - - return toString().equals(obj.toString()); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return toString(entries); - } - - private static String toString(Collection entries) { - StringBuilder builder = new StringBuilder(); - - boolean first = true; - for (RDN rdn : entries) { - if (first) { - first = false; - } else { - builder.append(","); - } - builder.append(rdn.toString()); - } - - return builder.toString(); - } - - /** - * @return first entry. Usually entry corresponding to something like "uid=joe" from the DN like "uid=joe,dc=something,dc=org" - */ - public RDN getFirstRdn() { - return entries.getFirst(); - } - - private static String unescapeValue(String escaped) { - // Something needed to handle non-String types? - return Rdn.unescapeValue(escaped).toString(); - } - - private static String escapeValue(String unescaped) { - // Something needed to handle non-String types? - return Rdn.escapeValue(unescaped); - } - - /** - * - * @return DN like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org". - * Returned DN will be new clone not related to the original DN instance. - * - */ - public LdapMapDn getParentDn() { - LinkedList parentDnEntries = new LinkedList<>(entries); - parentDnEntries.remove(); - return new LdapMapDn(parentDnEntries); - } - - public boolean isDescendantOf(LdapMapDn expectedParentDn) { - int parentEntriesCount = expectedParentDn.entries.size(); - - Deque myEntries = new LinkedList<>(this.entries); - boolean someRemoved = false; - while (myEntries.size() > parentEntriesCount) { - myEntries.removeFirst(); - someRemoved = true; - } - - String myEntriesParentStr = toString(myEntries).toLowerCase(); - String expectedParentDnStr = expectedParentDn.toString().toLowerCase(); - return someRemoved && myEntriesParentStr.equals(expectedParentDnStr); - } - - public void addFirst(String rdnName, String rdnValue) { - rdnValue = escapeValue(rdnValue); - entries.addFirst(new RDN(new SubEntry(rdnName, rdnValue))); - } - - public void addFirst(RDN entry) { - entries.addFirst(entry); - } - - private void addLast(RDN entry) { - entries.addLast(entry); - } - - /** - * Single RDN inside the DN. RDN usually consists of single item like "uid=john" . In some rare cases, it can have multiple - * sub-entries like "uid=john+sn=Doe" - */ - public static class RDN { - - private final List subs = new LinkedList<>(); - - private RDN() { - } - - private RDN(SubEntry subEntry) { - subs.add(subEntry); - } - - private void addSubEntry(SubEntry subEntry) { - subs.add(subEntry); - } - - /** - * @return Keys in the RDN. Returned list is the copy, which is not linked to the original RDN - */ - public List getAllKeys() { - return subs.stream().map(SubEntry::getAttrName).collect(Collectors.toList()); - } - - /** - * Assume that RDN is something like "uid=john", then this method will return "john" in case that attrName is "uid" . - * This is useful in case that RDN is multi-key - something like "uid=john+cn=John Doe" and we want to return just "john" as the value of "uid" - * - * The returned value will be unescaped - * - */ - public String getAttrValue(String attrName) { - for (SubEntry sub : subs) { - if (attrName.equalsIgnoreCase(sub.attrName)) { - return LdapMapDn.unescapeValue(sub.attrValue); - } - } - return null; - } - - public void setAttrValue(String attrName, String newAttrValue) { - for (SubEntry sub : subs) { - if (attrName.equalsIgnoreCase(sub.attrName)) { - sub.attrValue = escapeValue(newAttrValue); - return; - } - } - addSubEntry(new SubEntry(attrName, escapeValue(newAttrValue))); - } - - public boolean removeAttrValue(String attrName) { - SubEntry toRemove = null; - for (SubEntry sub : subs) { - if (attrName.equalsIgnoreCase(sub.attrName)) { - toRemove = sub; - } - } - - if (toRemove != null) { - subs.remove(toRemove); - return true; - } else { - return false; - } - } - - @Override - public String toString() { - return toString(true); - } - - /** - * - * @param escaped indicates whether return escaped or unescaped values. EG. "uid=john,comma" VS "uid=john\,comma" - */ - public String toString(boolean escaped) { - StringBuilder builder = new StringBuilder(); - - boolean first = true; - for (SubEntry subEntry : subs) { - if (first) { - first = false; - } else { - builder.append('+'); - } - builder.append(subEntry.toString(escaped)); - } - - return builder.toString(); - } - } - - private static class SubEntry { - private final String attrName; - private String attrValue; - - private SubEntry(String attrName, String attrValue) { - this.attrName = attrName; - this.attrValue = attrValue; - } - - private String getAttrName() { - return attrName; - } - - @Override - public String toString() { - return toString(true); - } - - private String toString(boolean escaped) { - String val = escaped ? attrValue : unescapeValue(attrValue); - return attrName + '=' + val; - } - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapObject.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapObject.java deleted file mode 100644 index 54256f0d57c..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapObject.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2016 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.storage.ldap.model; - -import org.jboss.logging.Logger; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; - -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author Marek Posolda - */ -public class LdapMapObject implements AbstractEntity { - - private static final Logger logger = Logger.getLogger(LdapMapObject.class); - - private String id; - private LdapMapDn dn; - - // In most cases, there is single "rdnAttributeName" . Usually "uid" or "cn" - private final List rdnAttributeNames = new LinkedList<>(); - - private final List objectClasses = new LinkedList<>(); - - // NOTE: names of read-only attributes are lower-cased to avoid case sensitivity issues - private final List readOnlyAttributeNames = new LinkedList<>(); - - private final Map> attributes = new HashMap<>(); - - // Copy of "attributes" containing lower-cased keys - private final Map> lowerCasedAttributes = new HashMap<>(); - - // range attributes are always read from 0 to max so just saving the top value - private final Map rangedAttributes = new HashMap<>(); - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LdapMapDn getDn() { - return dn; - } - - public void setDn(LdapMapDn dn) { - this.dn = dn; - } - - public List getObjectClasses() { - return objectClasses; - } - - public void setObjectClasses(Collection objectClasses) { - this.objectClasses.clear(); - this.objectClasses.addAll(objectClasses); - } - - public List getReadOnlyAttributeNames() { - return readOnlyAttributeNames; - } - - public void addReadOnlyAttributeName(String readOnlyAttribute) { - readOnlyAttributeNames.add(readOnlyAttribute.toLowerCase()); - } - - public void removeReadOnlyAttributeName(String readOnlyAttribute) { - readOnlyAttributeNames.remove(readOnlyAttribute.toLowerCase()); - } - - public List getRdnAttributeNames() { - return rdnAttributeNames; - } - - /** - * Useful when single value will be used as the "RDN" attribute. Which will be most of the cases - */ - public void setRdnAttributeName(String rdnAttributeName) { - this.rdnAttributeNames.clear(); - this.rdnAttributeNames.add(rdnAttributeName); - } - - public void setRdnAttributeNames(List rdnAttributeNames) { - this.rdnAttributeNames.clear(); - this.rdnAttributeNames.addAll(rdnAttributeNames); - } - - public void addRdnAttributeName(String rdnAttributeName) { - this.rdnAttributeNames.add(rdnAttributeName); - } - - public void setSingleAttribute(String attributeName, String attributeValue) { - Set asSet = new LinkedHashSet<>(); - asSet.add(attributeValue); - setAttribute(attributeName, asSet); - } - - public void setAttribute(String attributeName, Set attributeValue) { - attributes.put(attributeName, attributeValue); - lowerCasedAttributes.put(attributeName.toLowerCase(), attributeValue); - } - - // Case-insensitive - public String getAttributeAsString(String name) { - Set attrValue = lowerCasedAttributes.get(name.toLowerCase()); - if (attrValue == null || attrValue.size() == 0) { - return null; - } else if (attrValue.size() > 1) { - logger.warnf("Expected String but attribute '%s' has more values '%s' on object '%s' . Returning just first value", name, attrValue, dn); - } - - return attrValue.iterator().next(); - } - - // Case-insensitive. Return null if there is not value of attribute with given name or set with all values otherwise - public Set getAttributeAsSet(String name) { - Set values = lowerCasedAttributes.get(name.toLowerCase()); - return (values == null) ? null : new LinkedHashSet<>(values); - } - - public boolean isRangeComplete(String name) { - return !rangedAttributes.containsKey(name); - } - - public int getCurrentRange(String name) { - return rangedAttributes.get(name); - } - - public boolean isRangeCompleteForAllAttributes() { - return rangedAttributes.isEmpty(); - } - - public void addRangedAttribute(String name, int max) { - Integer current = rangedAttributes.get(name); - if (current == null || max > current) { - rangedAttributes.put(name, max); - } - } - - public void populateRangedAttribute(LdapMapObject obj, String name) { - Set newValues = obj.getAttributes().get(name); - if (newValues != null && attributes.containsKey(name)) { - attributes.get(name).addAll(newValues); - if (!obj.isRangeComplete(name)) { - addRangedAttribute(name, obj.getCurrentRange(name)); - } else { - rangedAttributes.remove(name); - } - } - } - - public Map> getAttributes() { - return attributes; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - - if (!getClass().isInstance(obj)) { - return false; - } - - LdapMapObject other = (LdapMapObject) obj; - - return getId() != null && other.getId() != null && getId().equals(other.getId()); - } - - @Override - public int hashCode() { - int result = getId() != null ? getId().hashCode() : 0; - result = 31 * result + (getId() != null ? getId().hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "LdapMapObject [ dn: " + dn + " , id: " + id + ", attributes: " + attributes + - ", readOnly attribute names: " + readOnlyAttributeNames + ", ranges: " + rangedAttributes + " ]"; - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapQuery.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapQuery.java deleted file mode 100644 index e29c6e48d46..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/model/LdapMapQuery.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2016 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.storage.ldap.model; - -import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.map.storage.ldap.LdapModelCriteriaBuilder; - -import javax.naming.directory.SearchControls; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import static java.util.Collections.unmodifiableSet; - -/** - * Default IdentityQuery implementation. - * - * LDAPQuery should be closed after use in case that pagination was used (initPagination was called) - * Closing LDAPQuery is very important in case ldapContextManager contains VaultSecret - * - * @author Shane Bryzak - */ -public class LdapMapQuery implements AutoCloseable { - - private static final Logger logger = Logger.getLogger(LdapMapQuery.class); - - private int offset; - private int limit; - private String searchDn; - private LdapModelCriteriaBuilder modelCriteriaBuilder; - - private final Set returningLdapAttributes = new LinkedHashSet<>(); - - // Contains just those returningLdapAttributes, which are read-only. They will be marked as read-only in returned LDAPObject instances as well - // NOTE: names of attributes are lower-cased to avoid case sensitivity issues (LDAP searching is usually case-insensitive, so we want to be as well) - private final Set returningReadOnlyLdapAttributes = new LinkedHashSet<>(); - private final Set objectClasses = new LinkedHashSet<>(); - - private final List mappers = new ArrayList<>(); - - private int searchScope = SearchControls.SUBTREE_SCOPE; - - public void setSearchDn(String searchDn) { - this.searchDn = searchDn; - } - - public void addObjectClasses(Collection objectClasses) { - this.objectClasses.addAll(objectClasses); - } - - public void addReturningLdapAttribute(String ldapAttributeName) { - this.returningLdapAttributes.add(ldapAttributeName); - } - - public void addReturningReadOnlyLdapAttribute(String ldapAttributeName) { - this.returningReadOnlyLdapAttributes.add(ldapAttributeName.toLowerCase()); - } - - public LdapMapQuery addMappers(Collection mappers) { - this.mappers.addAll(mappers); - return this; - } - - public void setSearchScope(int searchScope) { - this.searchScope = searchScope; - } - - public String getSearchDn() { - return this.searchDn; - } - - public Set getObjectClasses() { - return unmodifiableSet(this.objectClasses); - } - - public Set getReturningLdapAttributes() { - return unmodifiableSet(this.returningLdapAttributes); - } - - public Set getReturningReadOnlyLdapAttributes() { - return unmodifiableSet(this.returningReadOnlyLdapAttributes); - } - - public List getMappers() { - return mappers; - } - - public int getSearchScope() { - return searchScope; - } - - public int getLimit() { - return limit; - } - - public int getOffset() { - return offset; - } - - public LdapMapQuery setOffset(int offset) { - this.offset = offset; - return this; - } - - public LdapMapQuery setLimit(int limit) { - this.limit = limit; - return this; - } - - @Override - public void close() { - } - - public void setModelCriteriaBuilder(LdapModelCriteriaBuilder ldapModelCriteriaBuilder) { - this.modelCriteriaBuilder = ldapModelCriteriaBuilder; - } - - public LdapModelCriteriaBuilder getModelCriteriaBuilder() { - return modelCriteriaBuilder; - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/LdapRoleMapStorage.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/LdapRoleMapStorage.java deleted file mode 100644 index 0dedd6cb9a2..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/LdapRoleMapStorage.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.role; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelException; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StreamUtils; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.role.MapRoleEntity; - -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.chm.MapFieldPredicates; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder; -import org.keycloak.models.map.storage.ldap.MapModelCriteriaBuilderAssumingEqualForField; -import org.keycloak.models.map.storage.ldap.role.entity.LdapMapRoleEntityFieldDelegate; -import org.keycloak.models.map.storage.ldap.store.LdapMapIdentityStore; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; -import org.keycloak.models.map.storage.ldap.LdapMapStorage; -import org.keycloak.models.map.storage.ldap.model.LdapMapDn; -import org.keycloak.models.map.storage.ldap.model.LdapMapObject; -import org.keycloak.models.map.storage.ldap.model.LdapMapQuery; -import org.keycloak.models.map.storage.ldap.role.config.LdapMapRoleMapperConfig; -import org.keycloak.models.map.storage.ldap.role.entity.LdapRoleEntity; -import org.keycloak.provider.Provider; - -import javax.naming.NamingException; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class LdapRoleMapStorage extends LdapMapStorage implements Provider { - - private final StringKeyConverter keyConverter = new StringKeyConverter.StringKey(); - private final Set deletedKeys = new HashSet<>(); - private final LdapMapRoleMapperConfig roleMapperConfig; - private final LdapMapConfig ldapMapConfig; - private final LdapMapIdentityStore identityStore; - - public LdapRoleMapStorage(KeycloakSession session, Config.Scope config) { - this.roleMapperConfig = new LdapMapRoleMapperConfig(config); - this.ldapMapConfig = new LdapMapConfig(config); - this.identityStore = new LdapMapIdentityStore(session, ldapMapConfig); - session.enlistForClose(this); - } - - // interface matching the constructor of this class - public interface LdapRoleMapKeycloakTransactionFunction { - R apply(A a, B b); - } - - // TODO: entries might get stale if a DN of an entry changes due to changes in the entity in the same transaction - private final Map dns = new HashMap<>(); - - public String readIdByDn(String dn) { - // TODO: this might not be necessary if the LDAP server would support an extended OID - // https://ldapwiki.com/wiki/LDAP_SERVER_EXTENDED_DN_OID - - String id = dns.get(dn); - if (id == null) { - for (Map.Entry entry : entities.entrySet()) { - LdapMapObject ldap = entry.getValue().getLdapMapObject(); - if (ldap.getDn().toString().equals(dn)) { - id = ldap.getId(); - break; - } - } - } - if (id != null) { - return id; - } - - LdapMapQuery ldapQuery = new LdapMapQuery(); - - // For now, use same search scope, which is configured "globally" and used for user's search. - ldapQuery.setSearchScope(ldapMapConfig.getSearchScope()); - ldapQuery.setSearchDn(roleMapperConfig.getCommonRolesDn()); - - // TODO: read them properly to be able to store them in the transaction so they are cached?! - Collection roleObjectClasses = ldapMapConfig.getRoleObjectClasses(); - ldapQuery.addObjectClasses(roleObjectClasses); - - String rolesRdnAttr = roleMapperConfig.getRoleNameLdapAttribute(); - - ldapQuery.addReturningLdapAttribute(rolesRdnAttr); - - LdapMapDn.RDN rdn = LdapMapDn.fromString(dn).getFirstRdn(); - String key = rdn.getAllKeys().get(0); - String value = rdn.getAttrValue(key); - - LdapRoleModelCriteriaBuilder mcb = - new LdapRoleModelCriteriaBuilder(roleMapperConfig).compare(RoleModel.SearchableFields.NAME, ModelCriteriaBuilder.Operator.EQ, value); - mcb = mcb.withCustomFilter(roleMapperConfig.getCustomLdapFilter()); - ldapQuery.setModelCriteriaBuilder(mcb); - - List ldapObjects = identityStore.fetchQueryResults(ldapQuery); - if (ldapObjects.size() == 1) { - dns.put(dn, ldapObjects.get(0).getId()); - return ldapObjects.get(0).getId(); - } - return null; - } - - private MapModelCriteriaBuilder createCriteriaBuilderMap() { - // The realmId might not be set of instances retrieved by read(id) and we're still sure that they belong to the realm being searched. - // Therefore, ignore the field realmId when searching the instances that are stored within the transaction. - return new MapModelCriteriaBuilderAssumingEqualForField<>(keyConverter, MapFieldPredicates.getPredicates(RoleModel.class), RoleModel.SearchableFields.REALM_ID); - } - - @Override - public LdapMapRoleEntityFieldDelegate create(MapRoleEntity value) { - DeepCloner CLONER = new DeepCloner.Builder() - .constructor(MapRoleEntity.class, cloner -> new LdapMapRoleEntityFieldDelegate(new LdapRoleEntity(cloner, roleMapperConfig, this, value.getClientId()))) - .build(); - - LdapMapRoleEntityFieldDelegate mapped = (LdapMapRoleEntityFieldDelegate) CLONER.from(value); - - // LDAP should never use the UUID provided by the caller, as UUID is generated by the LDAP directory - mapped.setId(null); - // Roles as groups need to have at least one member on most directories. Add ourselves as a member as a dummy. - if (mapped.getLdapMapObject().getId() == null && mapped.getLdapMapObject().getAttributeAsSet(roleMapperConfig.getMembershipLdapAttribute()) == null) { - // insert our own name as dummy member of this role to avoid a schema conflict in LDAP - mapped.getLdapMapObject().setAttribute(roleMapperConfig.getMembershipLdapAttribute(), Stream.of(mapped.getLdapMapObject().getDn().toString()).collect(Collectors.toSet())); - } - - try { - // in order to get the ID, we need to write it to LDAP - identityStore.add(mapped.getLdapMapObject()); - // TODO: add a flag for temporary created roles until they are finally committed so that they don't show up in ready(query) in their temporary state - } catch (ModelException ex) { - if (value.getClientId() != null && ex.getCause() instanceof NamingException) { - // the client hasn't been created, therefore adding it here - LdapMapObject client = new LdapMapObject(); - client.setObjectClasses(Arrays.asList("top", "organizationalUnit")); - client.setRdnAttributeName("ou"); - client.setDn(LdapMapDn.fromString(roleMapperConfig.getRolesDn(mapped.getClientId()))); - client.setSingleAttribute("ou", mapped.getClientId()); - identityStore.add(client); - - tasksOnRollback.add(new DeleteOperation() { - @Override - public void execute() { - identityStore.remove(client); - } - }); - - // retry creation of client role - identityStore.add(mapped.getLdapMapObject()); - } - } - - entities.put(mapped.getId(), mapped); - - tasksOnRollback.add(new DeleteOperation() { - @Override - public void execute() { - identityStore.remove(mapped.getLdapMapObject()); - entities.remove(mapped.getId()); - } - }); - - return mapped; - } - - @Override - public boolean delete(String key) { - LdapMapRoleEntityFieldDelegate read = read(key); - if (read == null) { - throw new ModelException("unable to read entity with key " + key); - } - if (!deletedKeys.contains((key))) { - // avoid enlisting LDAP removal twice if client calls it twice - deletedKeys.add(key); - tasksOnCommit.add(new DeleteOperation() { - @Override - public void execute() { - identityStore.remove(read.getLdapMapObject()); - // once removed from LDAP, avoid updating a modified entity in LDAP. - entities.remove(read.getId()); - } - }); - } - return true; - } - - public LdapRoleEntity readLdap(String key) { - LdapMapRoleEntityFieldDelegate read = read(key); - if (read == null) { - return null; - } else { - return read.getEntityFieldDelegate(); - } - } - - @Override - public LdapMapRoleEntityFieldDelegate read(String key) { - if (deletedKeys.contains(key)) { - return null; - } - - // reuse an existing live entity - LdapMapRoleEntityFieldDelegate val = entities.get(key); - - if (val == null) { - - // try to look it up as a realm role - val = lookupEntityById(key, null); - - if (val == null) { - // try to find out the client ID - LdapMapQuery ldapQuery = new LdapMapQuery(); - - // For now, use same search scope, which is configured "globally" and used for user's search. - ldapQuery.setSearchScope(ldapMapConfig.getSearchScope()); - - // remove prefix with placeholder to allow for a broad search - String sdn = roleMapperConfig.getClientRolesDn(); - ldapQuery.setSearchDn(sdn.replaceAll(".*\\{0},", "")); - - LdapMapObject ldapObject = identityStore.fetchById(key, ldapQuery); - if (ldapObject != null) { - // as the client ID is now known, search again with the specific configuration - LdapMapDn.RDN firstRdn = ldapObject.getDn().getParentDn().getFirstRdn(); - String clientId = firstRdn.getAttrValue(firstRdn.getAllKeys().get(0)); - // lookup with clientId, as the search above might have been broader than a restricted search - val = lookupEntityById(key, clientId); - } - } - - if (val != null) { - entities.put(key, val); - } - - } - return val; - } - - private LdapMapRoleEntityFieldDelegate lookupEntityById(String id, String clientId) { - LdapMapQuery ldapQuery = getLdapQuery(clientId); - - LdapMapObject ldapObject = identityStore.fetchById(id, ldapQuery); - if (ldapObject != null) { - return new LdapMapRoleEntityFieldDelegate(new LdapRoleEntity(ldapObject, roleMapperConfig, this, clientId)); - } - return null; - } - - @Override - public Stream read(QueryParameters queryParameters) { - LdapRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder() - .flashToModelCriteriaBuilder(createLdapModelCriteriaBuilder()); - - Boolean isClientRole = mcb.isClientRole(); - String clientId = mcb.getClientId(); - - LdapMapQuery ldapQuery = getLdapQuery(clientId); - if (isClientRole == null) { - ldapQuery.setSearchDn(roleMapperConfig.getCommonRolesDn()); - } - - mcb = mcb.withCustomFilter(roleMapperConfig.getCustomLdapFilter()); - ldapQuery.setModelCriteriaBuilder(mcb); - - Stream ldapStream; - - MapModelCriteriaBuilder mapMcb = queryParameters.getModelCriteriaBuilder().flashToModelCriteriaBuilder(createCriteriaBuilderMap()); - - Stream existingEntities = entities.entrySet().stream() - .filter(me -> mapMcb.getKeyFilter().test(keyConverter.fromString(me.getKey())) && !deletedKeys.contains(me.getKey())) - .map(Map.Entry::getValue) - .filter(mapMcb.getEntityFilter()) - // snapshot list - .collect(Collectors.toList()).stream(); - - // current approach: combine the results in a correct way from existing entities in the transaction and LDAP - // problem here: pagination doesn't work any more as results are retrieved from both, and then need to be sorted - // possible alternative: use search criteria only on LDAP, and replace found entities with those stored in transaction already - // this will then not find additional entries modified or created in this transaction - - try { - List ldapObjects = identityStore.fetchQueryResults(ldapQuery); - - ldapStream = ldapObjects.stream().map(ldapMapObject -> { - // we might have fetch client and realm roles at the same time, now try to decode what is what - StreamUtils.Pair client = getClientId(ldapMapObject.getDn()); - if (client == null) { - return null; - } - LdapMapRoleEntityFieldDelegate entity = new LdapMapRoleEntityFieldDelegate(new LdapRoleEntity(ldapMapObject, roleMapperConfig, this, client.getV())); - LdapMapRoleEntityFieldDelegate existingEntry = entities.get(entity.getId()); - if (existingEntry != null) { - // this entry will be part of the existing entities - return null; - } - entities.put(entity.getId(), entity); - return (MapRoleEntity) entity; - }) - .filter(Objects::nonNull) - .filter(me -> !deletedKeys.contains(me.getId())) - // re-apply filters about client roles that we might have skipped for LDAP - .filter(me -> mapMcb.getKeyFilter().test(me.getId())) - .filter(me -> mapMcb.getEntityFilter().test(me)) - // snapshot list, as the contents depends on entities and also updates the entities, - // and two streams open at the same time could otherwise interfere - .collect(Collectors.toList()).stream(); - } catch (ModelException ex) { - if (clientId != null && ex.getCause() instanceof NamingException) { - // the client wasn't found in LDAP, assume an empty result - ldapStream = Stream.empty(); - } else { - throw ex; - } - } - - ldapStream = Stream.concat(ldapStream, existingEntities); - - if (!queryParameters.getOrderBy().isEmpty()) { - ldapStream = ldapStream.sorted(MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream())); - } - if (queryParameters.getOffset() != null) { - ldapStream = ldapStream.skip(queryParameters.getOffset()); - } - if (queryParameters.getLimit() != null) { - ldapStream = ldapStream.limit(queryParameters.getLimit()); - } - - return ldapStream; - } - - private StreamUtils.Pair getClientId(LdapMapDn dn) { - if (dn.getParentDn().equals(LdapMapDn.fromString(roleMapperConfig.getRealmRolesDn()))) { - return new StreamUtils.Pair<>(false, null); - } - String clientsDnWildcard = roleMapperConfig.getClientRolesDn(); - if (clientsDnWildcard != null) { - clientsDnWildcard = clientsDnWildcard.replaceAll(".*\\{0},", ""); - if (dn.getParentDn().getParentDn().equals(LdapMapDn.fromString(clientsDnWildcard))) { - LdapMapDn.RDN firstRdn = dn.getParentDn().getFirstRdn(); - return new StreamUtils.Pair<>(true, firstRdn.getAttrValue(firstRdn.getAllKeys().get(0))); - } - } - return null; - } - - private LdapMapQuery getLdapQuery(String clientId) { - LdapMapQuery ldapMapQuery = new LdapMapQuery(); - - // For now, use same search scope, which is configured "globally" and used for user's search. - ldapMapQuery.setSearchScope(ldapMapConfig.getSearchScope()); - - String rolesDn = roleMapperConfig.getRolesDn(clientId); - ldapMapQuery.setSearchDn(rolesDn); - - Collection roleObjectClasses = ldapMapConfig.getRoleObjectClasses(); - ldapMapQuery.addObjectClasses(roleObjectClasses); - - String rolesRdnAttr = roleMapperConfig.getRoleNameLdapAttribute(); - - ldapMapQuery.addReturningLdapAttribute(rolesRdnAttr); - ldapMapQuery.addReturningLdapAttribute("description"); - ldapMapQuery.addReturningLdapAttribute(roleMapperConfig.getMembershipLdapAttribute()); - roleMapperConfig.getRoleAttributes().forEach(ldapMapQuery::addReturningLdapAttribute); - return ldapMapQuery; - } - - @Override - public void commit() { - super.commit(); - for (MapTaskWithValue mapTaskWithValue : tasksOnCommit) { - mapTaskWithValue.execute(); - } - - entities.forEach((entityKey, entity) -> { - if (entity.isUpdated()) { - identityStore.update(entity.getLdapMapObject()); - } - }); - // once the commit is complete, clear the local storage to avoid problems when rollback() is called later - // due to a different transaction failing. - tasksOnCommit.clear(); - entities.clear(); - tasksOnRollback.clear(); - } - - @Override - public void rollback() { - super.rollback(); - Iterator iterator = tasksOnRollback.descendingIterator(); - while (iterator.hasNext()) { - iterator.next().execute(); - } - } - - protected LdapRoleModelCriteriaBuilder createLdapModelCriteriaBuilder() { - return new LdapRoleModelCriteriaBuilder(roleMapperConfig); - } - - @Override - public void close() { - identityStore.close(); - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/LdapRoleModelCriteriaBuilder.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/LdapRoleModelCriteriaBuilder.java deleted file mode 100644 index aff4284f930..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/LdapRoleModelCriteriaBuilder.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.role; - -import org.keycloak.models.ModelException; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.storage.CriterionNotSupportedException; -import org.keycloak.models.map.storage.ldap.LdapModelCriteriaBuilder; -import org.keycloak.models.map.storage.ldap.store.LdapMapEscapeStrategy; -import org.keycloak.models.map.storage.ldap.role.config.LdapMapRoleMapperConfig; -import org.keycloak.models.map.storage.ldap.role.entity.LdapRoleEntity; -import org.keycloak.storage.SearchableModelField; - -import java.util.ArrayList; -import java.util.Objects; -import java.util.function.Supplier; - -public class LdapRoleModelCriteriaBuilder extends LdapModelCriteriaBuilder { - - private final LdapMapRoleMapperConfig roleMapperConfig; - - public String getClientId() { - return clientId; - } - - public Boolean isClientRole() { - return isClientRole; - } - - public String getRealmId() { - return realmId; - } - - private String clientId; - - private Boolean isClientRole; - - private String realmId; - - @Override - public LdapRoleModelCriteriaBuilder and(LdapRoleModelCriteriaBuilder... builders) { - LdapRoleModelCriteriaBuilder and = super.and(builders); - for (LdapRoleModelCriteriaBuilder builder : builders) { - if (builder.isClientRole != null) { - if (and.isClientRole != null && !Objects.equals(and.isClientRole, builder.isClientRole)) { - throw new ModelException("isClientRole must be specified in query only once"); - } - and.isClientRole = builder.isClientRole; - } - if (builder.clientId != null) { - if (and.clientId != null && !Objects.equals(and.clientId, builder.clientId)) { - throw new ModelException("clientId must be specified in query only once"); - } - and.clientId = builder.clientId; - } - if (builder.realmId != null) { - if (and.realmId != null && !Objects.equals(and.realmId, builder.realmId)) { - throw new ModelException("realmId must be specified in query only once"); - } - and.realmId = builder.realmId; - } - } - return and; - } - - @Override - public LdapRoleModelCriteriaBuilder or(LdapRoleModelCriteriaBuilder... builders) { - LdapRoleModelCriteriaBuilder or = super.or(builders); - for (LdapRoleModelCriteriaBuilder builder : builders) { - if (builder.isClientRole != null) { - throw new ModelException("isClientRole not supported in OR condition"); - } - if (builder.clientId != null) { - throw new ModelException("clientId not supported in OR condition"); - } - if (builder.realmId != null) { - throw new ModelException("realmId not supported in OR condition"); - } - } - return or; - } - - @Override - public LdapRoleModelCriteriaBuilder not(LdapRoleModelCriteriaBuilder builder) { - LdapRoleModelCriteriaBuilder not = super.not(builder); - if (builder.isClientRole != null) { - throw new ModelException("isClientRole not supported in NOT condition"); - } - if (builder.clientId != null) { - throw new ModelException("clientId not supported in NOT condition"); - } - if (builder.realmId != null) { - throw new ModelException("realmId not supported in NOT condition"); - } - return not; - } - - public LdapRoleModelCriteriaBuilder(LdapMapRoleMapperConfig roleMapperConfig) { - super(predicateFunc -> new LdapRoleModelCriteriaBuilder(roleMapperConfig, predicateFunc)); - this.roleMapperConfig = roleMapperConfig; - } - - private LdapRoleModelCriteriaBuilder(LdapMapRoleMapperConfig roleMapperConfig, Supplier predicateFunc) { - super(pf -> new LdapRoleModelCriteriaBuilder(roleMapperConfig, pf), predicateFunc); - this.roleMapperConfig = roleMapperConfig; - } - - @Override - public LdapRoleModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - switch (op) { - case EQ: - if (modelField == RoleModel.SearchableFields.CLIENT_ID) { - LdapRoleModelCriteriaBuilder result = new LdapRoleModelCriteriaBuilder(roleMapperConfig, StringBuilder::new); - result.clientId = (String) value[0]; - return result; - } else if (modelField == RoleModel.SearchableFields.REALM_ID) { - LdapRoleModelCriteriaBuilder result = new LdapRoleModelCriteriaBuilder(roleMapperConfig, StringBuilder::new); - result.realmId = (String) value[0]; - return result; - } else if (modelField == RoleModel.SearchableFields.NAME) { - // validateValue(value, modelField, op, String.class); - String field = modelFieldNameToLdap(roleMapperConfig, modelField); - return new LdapRoleModelCriteriaBuilder(roleMapperConfig, - () -> equal(field, value[0], LdapMapEscapeStrategy.DEFAULT, false)); - } else if (modelField == RoleModel.SearchableFields.COMPOSITE_ROLE) { - // Not supported at the moment - return new LdapRoleModelCriteriaBuilder(roleMapperConfig, StringBuilder::new); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case NE: - if (modelField == RoleModel.SearchableFields.NAME) { - // validateValue(value, modelField, op, String.class); - String field = modelFieldNameToLdap(roleMapperConfig, modelField); - return not(new LdapRoleModelCriteriaBuilder(roleMapperConfig, - () -> equal(field, value[0], LdapMapEscapeStrategy.DEFAULT, false))); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case ILIKE: - case LIKE: - if (modelField == RoleModel.SearchableFields.NAME || - modelField == RoleModel.SearchableFields.DESCRIPTION) { - // validateValue(value, modelField, op, String.class); - // first escape all elements of the string (which would not escape the percent sign) - // then replace percent sign with the wildcard character asterisk - // the result should then be used unescaped in the condition. - String v = LdapMapEscapeStrategy.DEFAULT.escape(String.valueOf(value[0])).replaceAll("%", "*"); - // TODO: there is no placeholder for a single character wildcard ... use multicharacter wildcard instead? - String field = modelFieldNameToLdap(roleMapperConfig, modelField); - return new LdapRoleModelCriteriaBuilder(roleMapperConfig, () -> { - if (v.equals("**")) { - // wildcard everything is not well-understood by LDAP and will result in "ERR_01101_NULL_LENGTH The length should not be 0" - return new StringBuilder(); - } else { - return equal(field, v, LdapMapEscapeStrategy.NON_ASCII_CHARS_ONLY, false); - } - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - - case IN: - if (modelField == RoleModel.SearchableFields.NAME || - modelField == RoleModel.SearchableFields.DESCRIPTION || - modelField == RoleModel.SearchableFields.ID) { - String field = modelFieldNameToLdap(roleMapperConfig, modelField); - return new LdapRoleModelCriteriaBuilder(roleMapperConfig, () -> { - Object[] v; - if (value[0] instanceof ArrayList) { - v = ((ArrayList) value[0]).toArray(); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - return in(field, v, false); - }); - } else { - throw new CriterionNotSupportedException(modelField, op); - } - case EXISTS: - case NOT_EXISTS: - if (modelField == RoleModel.SearchableFields.CLIENT_ID) { - LdapRoleModelCriteriaBuilder result = new LdapRoleModelCriteriaBuilder(roleMapperConfig, StringBuilder::new); - result.isClientRole = op == Operator.EXISTS; - return result; - } - - default: - throw new CriterionNotSupportedException(modelField, op); - } - } - - private String modelFieldNameToLdap(LdapMapRoleMapperConfig roleMapperConfig, SearchableModelField modelField) { - if (modelField == RoleModel.SearchableFields.NAME) { - return roleMapperConfig.getRoleNameLdapAttribute(); - } else if (modelField == RoleModel.SearchableFields.ID) { - return roleMapperConfig.getLdapMapConfig().getUuidLDAPAttributeName(); - } else if (modelField == RoleModel.SearchableFields.DESCRIPTION) { - return "description"; - } else { - throw new CriterionNotSupportedException(modelField, null); - } - } - - public LdapRoleModelCriteriaBuilder withCustomFilter(String customFilter) { - if (customFilter != null && toString().length() > 0) { - return and(this, new LdapRoleModelCriteriaBuilder(roleMapperConfig, () -> new StringBuilder(customFilter))); - } - return this; - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/config/LdapMapRoleMapperConfig.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/config/LdapMapRoleMapperConfig.java deleted file mode 100644 index 5a70940e7b7..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/config/LdapMapRoleMapperConfig.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.role.config; - -import org.keycloak.Config; -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.map.storage.ldap.config.LdapMapCommonGroupMapperConfig; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; -import org.keycloak.models.map.storage.ldap.model.LdapMapDn; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; - -public class LdapMapRoleMapperConfig extends LdapMapCommonGroupMapperConfig { - - private final Config.Scope config; - private final LdapMapConfig ldapMapConfig; - - public LdapMapRoleMapperConfig(Config.Scope config) { - super(new ComponentModel() { - @Override - public MultivaluedHashMap getConfig() { - return new MultivaluedHashMap() { - @Override - public String getFirst(String key) { - return config.get(key); - } - }; - } - }); - this.config = config; - this.ldapMapConfig = new LdapMapConfig(config); - } - - public String getRealmRolesDn() { - String rolesDn = config.get(REALM_ROLES_DN); - if (rolesDn == null) { - throw new ModelException("Roles DN is null! Check your configuration"); - } - return rolesDn; - } - - public String getCommonRolesDn() { - String rolesDn = config.get(COMMON_ROLES_DN); - if (rolesDn == null) { - throw new ModelException("Roles DN is null! Check your configuration"); - } - return rolesDn; - } - - public String getClientRolesDn() { - String rolesDn = config.get(CLIENT_ROLES_DN); - if (rolesDn == null) { - throw new ModelException("Roles DN is null! Check your configuration"); - } - return rolesDn; - } - - public String getRolesDn(String clientId) { - String rolesDn; - boolean isClientRole = clientId != null; - if (! isClientRole) { - rolesDn = mapperModel.getConfig().getFirst(REALM_ROLES_DN); - } else { - rolesDn = config.get(CLIENT_ROLES_DN); - if (rolesDn != null) { - LdapMapDn dn = LdapMapDn.fromString(rolesDn); - LdapMapDn.RDN firstRdn = dn.getFirstRdn(); - for (String key : firstRdn.getAllKeys()) { - firstRdn.setAttrValue(key, firstRdn.getAttrValue(key).replaceAll("\\{0}", Matcher.quoteReplacement(clientId))); - } - rolesDn = dn.toString(); - } - } - if (rolesDn == null) { - throw new ModelException("Roles DN is null! Check your configuration"); - } - return rolesDn; - } - - public Set getRoleAttributes() { - String roleAttributes = mapperModel.getConfig().getFirst("role.attributes"); - if (roleAttributes == null) { - roleAttributes = ""; - } - return new HashSet<>(Arrays.asList(roleAttributes.trim().split("\\s+"))); - } - - // LDAP DN where are realm roles of this tree saved. - public static final String REALM_ROLES_DN = "roles.realm.dn"; - - // LDAP DN where are client roles of this tree saved. - public static final String CLIENT_ROLES_DN = "roles.client.dn"; - - // LDAP DN to find both client and realm roles. - public static final String COMMON_ROLES_DN = "roles.common.dn"; - - // Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be "cn" - public static final String ROLE_NAME_LDAP_ATTRIBUTE = "role.name.ldap.attribute"; - - // Object classes of the role object. - public static final String ROLE_OBJECT_CLASSES = "role.object.classes"; - - // Customized LDAP filter which is added to the whole LDAP query - public static final String ROLES_LDAP_FILTER = "roles.ldap.filter"; - - // See UserRolesRetrieveStrategy - public static final String LOAD_ROLES_BY_MEMBER_ATTRIBUTE = "LOAD_ROLES_BY_MEMBER_ATTRIBUTE"; - public static final String GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE = "GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE"; - public static final String LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY = "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"; - - public String getRoleNameLdapAttribute() { - String rolesRdnAttr = mapperModel.getConfig().getFirst(ROLE_NAME_LDAP_ATTRIBUTE); - return rolesRdnAttr!=null ? rolesRdnAttr : LDAPConstants.CN; - } - - @Override - public String getLDAPGroupNameLdapAttribute() { - return getRoleNameLdapAttribute(); - } - - public String getCustomLdapFilter() { - return mapperModel.getConfig().getFirst(ROLES_LDAP_FILTER); - } - - public String getUserRolesRetrieveStrategy() { - String strategyString = mapperModel.getConfig().getFirst(USER_ROLES_RETRIEVE_STRATEGY); - return strategyString!=null ? strategyString : LOAD_ROLES_BY_MEMBER_ATTRIBUTE; - } - - public LdapMapConfig getLdapMapConfig() { - return ldapMapConfig; - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/entity/LdapMapRoleEntityFieldDelegate.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/entity/LdapMapRoleEntityFieldDelegate.java deleted file mode 100644 index 778bb15663d..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/entity/LdapMapRoleEntityFieldDelegate.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.role.entity; - -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.role.MapRoleEntityFieldDelegate; -import org.keycloak.models.map.storage.ldap.model.LdapMapObject; - -public class LdapMapRoleEntityFieldDelegate extends MapRoleEntityFieldDelegate { - - public LdapMapRoleEntityFieldDelegate(EntityFieldDelegate entityFieldDelegate) { - super(entityFieldDelegate); - } - - @Override - public LdapRoleEntity getEntityFieldDelegate() { - return (LdapRoleEntity) super.getEntityFieldDelegate(); - } - - @Override - public boolean isUpdated() { - // TODO: EntityFieldDelegate.isUpdated is broken, as it is never updated - return getEntityFieldDelegate().isUpdated(); - } - - public LdapMapObject getLdapMapObject() { - return getEntityFieldDelegate().getLdapMapObject(); - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/entity/LdapRoleEntity.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/entity/LdapRoleEntity.java deleted file mode 100644 index 7bbe5e6bf57..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/role/entity/LdapRoleEntity.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.role.entity; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - -import org.apache.commons.lang.NotImplementedException; -import org.keycloak.models.ModelException; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.role.MapRoleEntityFields; -import org.keycloak.models.map.storage.ldap.model.LdapMapDn; -import org.keycloak.models.map.storage.ldap.model.LdapMapObject; -import org.keycloak.models.map.storage.ldap.role.config.LdapMapRoleMapperConfig; -import org.keycloak.models.map.storage.ldap.role.LdapRoleMapStorage; - -public class LdapRoleEntity extends UpdatableEntity.Impl implements EntityFieldDelegate { - - private final LdapMapObject ldapMapObject; - private final LdapMapRoleMapperConfig roleMapperConfig; - private final LdapRoleMapStorage store; - private final String clientId; - - private static final EnumMap> SETTERS = new EnumMap<>(MapRoleEntityFields.class); - static { - SETTERS.put(MapRoleEntityFields.DESCRIPTION, (e, v) -> e.setDescription((String) v)); - SETTERS.put(MapRoleEntityFields.ID, (e, v) -> e.setId((String) v)); - SETTERS.put(MapRoleEntityFields.REALM_ID, (e, v) -> e.setRealmId((String) v)); - SETTERS.put(MapRoleEntityFields.CLIENT_ID, (e, v) -> e.setClientId((String) v)); - //noinspection unchecked - SETTERS.put(MapRoleEntityFields.ATTRIBUTES, (e, v) -> e.setAttributes((Map>) v)); - //noinspection unchecked - SETTERS.put(MapRoleEntityFields.COMPOSITE_ROLES, (e, v) -> e.setCompositeRoles((Set) v)); - SETTERS.put(MapRoleEntityFields.NAME, (e, v) -> e.setName((String) v)); - } - - private static final EnumMap> GETTERS = new EnumMap<>(MapRoleEntityFields.class); - static { - GETTERS.put(MapRoleEntityFields.DESCRIPTION, LdapRoleEntity::getDescription); - GETTERS.put(MapRoleEntityFields.ID, LdapRoleEntity::getId); - GETTERS.put(MapRoleEntityFields.REALM_ID, LdapRoleEntity::getRealmId); - GETTERS.put(MapRoleEntityFields.CLIENT_ID, LdapRoleEntity::getClientId); - GETTERS.put(MapRoleEntityFields.ATTRIBUTES, LdapRoleEntity::getAttributes); - GETTERS.put(MapRoleEntityFields.COMPOSITE_ROLES, LdapRoleEntity::getCompositeRoles); - GETTERS.put(MapRoleEntityFields.NAME, LdapRoleEntity::getName); - } - - private static final EnumMap> ADDERS = new EnumMap<>(MapRoleEntityFields.class); - static { - ADDERS.put(MapRoleEntityFields.COMPOSITE_ROLES, (e, v) -> e.addCompositeRole((String) v)); - } - - private static final EnumMap> REMOVERS = new EnumMap<>(MapRoleEntityFields.class); - static { - REMOVERS.put(MapRoleEntityFields.COMPOSITE_ROLES, (e, v) -> { e.removeCompositeRole((String) v); return null; }); - } - - public LdapRoleEntity(DeepCloner cloner, LdapMapRoleMapperConfig roleMapperConfig, LdapRoleMapStorage store, String clientId) { - ldapMapObject = new LdapMapObject(); - ldapMapObject.setObjectClasses(Arrays.asList("top", "groupOfNames")); - ldapMapObject.setRdnAttributeName(roleMapperConfig.getRoleNameLdapAttribute()); - this.roleMapperConfig = roleMapperConfig; - this.store = store; - this.clientId = clientId; - } - - public LdapRoleEntity(LdapMapObject ldapMapObject, LdapMapRoleMapperConfig roleMapperConfig, LdapRoleMapStorage store, String clientId) { - this.ldapMapObject = ldapMapObject; - this.roleMapperConfig = roleMapperConfig; - this.store = store; - this.clientId = clientId; - } - - public String getId() { - return ldapMapObject.getId(); - } - - public void setId(String id) { - this.updated |= !Objects.equals(getId(), id); - ldapMapObject.setId(id); - } - - - public Map> getAttributes() { - Map> result = new HashMap<>(); - for (String roleAttribute : roleMapperConfig.getRoleAttributes()) { - Set attrs = ldapMapObject.getAttributeAsSet(roleAttribute); - if (attrs != null) { - result.put(roleAttribute, new ArrayList<>(attrs)); - } - } - return result; - } - - public void setAttributes(Map> attributes) { - // store all attributes - if (attributes != null) { - attributes.forEach(this::setAttribute); - } - // clear attributes not in the list - for (String roleAttribute : roleMapperConfig.getRoleAttributes()) { - if (attributes == null || !attributes.containsKey(roleAttribute)) { - removeAttribute(roleAttribute); - } - } - } - - public List getAttribute(String name) { - if (!roleMapperConfig.getRoleAttributes().contains(name)) { - throw new ModelException("can't read attribute '" + name + "' as it is not supported"); - } - return new ArrayList<>(ldapMapObject.getAttributeAsSet(name)); - } - - public void setAttribute(String name, List value) { - if (!roleMapperConfig.getRoleAttributes().contains(name)) { - throw new ModelException("can't set attribute '" + name + "' as it is not supported"); - } - if ((ldapMapObject.getAttributeAsSet(name) == null && (value == null || value.size() == 0)) || - Objects.equals(ldapMapObject.getAttributeAsSet(name), new HashSet<>(value))) { - return; - } - if (ldapMapObject.getReadOnlyAttributeNames().contains(name)) { - throw new ModelException("can't write attribute '" + name + "' as it is not writeable"); - } - ldapMapObject.setAttribute(name, new HashSet<>(value)); - this.updated = true; - } - - public void removeAttribute(String name) { - if (!roleMapperConfig.getRoleAttributes().contains(name)) { - throw new ModelException("can't write attribute '" + name + "' as it is not supported"); - } - if (ldapMapObject.getAttributeAsSet(name) == null || ldapMapObject.getAttributeAsSet(name).size() == 0) { - return; - } - ldapMapObject.setAttribute(name, null); - this.updated = true; - } - - public String getRealmId() { - return null; - } - - public String getClientId() { - return clientId; - } - - public String getName() { - return ldapMapObject.getAttributeAsString(roleMapperConfig.getRoleNameLdapAttribute()); - } - - public String getDescription() { - return ldapMapObject.getAttributeAsString("description"); - } - - public void setRealmId(String realmId) { - // we'll not store this information, as LDAP store might be used from different realms - } - - public void setClientId(String clientId) { - if (!Objects.equals(this.getClientId(), clientId)) { - throw new NotImplementedException(); - } - } - - public void setName(String name) { - this.updated |= !Objects.equals(getName(), name); - ldapMapObject.setSingleAttribute(roleMapperConfig.getRoleNameLdapAttribute(), name); - LdapMapDn dn = LdapMapDn.fromString(roleMapperConfig.getRolesDn(clientId)); - dn.addFirst(roleMapperConfig.getRoleNameLdapAttribute(), name); - ldapMapObject.setDn(dn); - } - - public void setDescription(String description) { - this.updated |= !Objects.equals(getDescription(), description); - if (description != null) { - ldapMapObject.setSingleAttribute("description", description); - } else if (getDescription() != null) { - ldapMapObject.setAttribute("description", null); - } - } - - public Set getCompositeRoles() { - Set members = ldapMapObject.getAttributeAsSet(roleMapperConfig.getMembershipLdapAttribute()); - if (members == null) { - members = new HashSet<>(); - } - HashSet compositeRoles = new HashSet<>(); - for (String member : members) { - if (member.equals(ldapMapObject.getDn().toString())) { - continue; - } - if (!member.startsWith(roleMapperConfig.getRoleNameLdapAttribute())) { - // this is a real user, not a composite role, ignore - // TODO: this will not work if users and role use the same! - continue; - } - String roleId = store.readIdByDn(member); - if (roleId == null) { - throw new NotImplementedException(); - } - compositeRoles.add(roleId); - } - return compositeRoles; - } - - public void setCompositeRoles(Set compositeRoles) { - HashSet translatedCompositeRoles = new HashSet<>(); - if (compositeRoles != null) { - for (String compositeRole : compositeRoles) { - LdapRoleEntity ldapRole = store.readLdap(compositeRole); - translatedCompositeRoles.add(ldapRole.getLdapMapObject().getDn().toString()); - } - } - Set members = ldapMapObject.getAttributeAsSet(roleMapperConfig.getMembershipLdapAttribute()); - if (members == null) { - members = new HashSet<>(); - } - for (String member : members) { - if (!member.startsWith(roleMapperConfig.getRoleNameLdapAttribute())) { - // this is a real user, not a composite role, ignore - // TODO: this will not work if users and role use the same! - translatedCompositeRoles.add(member); - } - } - if (!translatedCompositeRoles.equals(members)) { - ldapMapObject.setAttribute(roleMapperConfig.getMembershipLdapAttribute(), members); - this.updated = true; - } - } - - public void addCompositeRole(String roleId) { - LdapRoleEntity ldapRole = store.readLdap(roleId); - Set members = ldapMapObject.getAttributeAsSet(roleMapperConfig.getMembershipLdapAttribute()); - if (members == null) { - members = new HashSet<>(); - } - members.add(ldapRole.getLdapMapObject().getDn().toString()); - ldapMapObject.setAttribute(roleMapperConfig.getMembershipLdapAttribute(), members); - this.updated = true; - } - - public void removeCompositeRole(String roleId) { - LdapRoleEntity ldapRole = store.readLdap(roleId); - Set members = ldapMapObject.getAttributeAsSet(roleMapperConfig.getMembershipLdapAttribute()); - if (members == null) { - members = new HashSet<>(); - } - members.remove(ldapRole.getLdapMapObject().getDn().toString()); - ldapMapObject.setAttribute(roleMapperConfig.getMembershipLdapAttribute(), members); - this.updated = true; - } - - public LdapMapObject getLdapMapObject() { - return ldapMapObject; - } - - @Override - public > & EntityField> void set(EF field, T value) { - BiConsumer consumer = SETTERS.get(field); - if (consumer == null) { - throw new ModelException("unsupported field for setters " + field); - } - consumer.accept(this, value); - } - - @Override - public > & EntityField> void collectionAdd(EF field, T value) { - BiConsumer consumer = ADDERS.get(field); - if (consumer == null) { - throw new ModelException("unsupported field for setters " + field); - } - consumer.accept(this, value); - } - - @Override - public > & EntityField> Object collectionRemove(EF field, T value) { - BiFunction consumer = REMOVERS.get(field); - if (consumer == null) { - throw new ModelException("unsupported field for setters " + field); - } - return consumer.apply(this, value); - } - - @Override - public > & EntityField> Object get(EF field) { - Function consumer = GETTERS.get(field); - if (consumer == null) { - throw new ModelException("unsupported field for getters " + field); - } - return consumer.apply(this); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapGet(EF field, K key) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void mapPut(EF field, K key, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapRemove(EF field, K key) { - throw new UnsupportedOperationException("Not supported yet."); - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapContextManager.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapContextManager.java deleted file mode 100644 index a8af3e55bdc..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapContextManager.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; -import org.keycloak.truststore.TruststoreProvider; -import org.keycloak.vault.VaultCharSecret; - -import javax.naming.AuthenticationException; -import javax.naming.Context; -import javax.naming.NamingException; -import javax.naming.ldap.InitialLdapContext; -import javax.naming.ldap.LdapContext; -import javax.naming.ldap.StartTlsRequest; -import javax.naming.ldap.StartTlsResponse; -import javax.net.ssl.SSLSocketFactory; -import java.io.IOException; -import java.nio.CharBuffer; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Optional; -import java.util.Properties; - -import static javax.naming.Context.SECURITY_CREDENTIALS; - -/** - * @author mhajas - */ -public final class LdapMapContextManager implements AutoCloseable { - - private static final Logger logger = Logger.getLogger(LdapMapContextManager.class); - - private final KeycloakSession session; - private final LdapMapConfig ldapMapConfig; - private StartTlsResponse tlsResponse; - - private VaultCharSecret vaultCharSecret = new VaultCharSecret() { - @Override - public Optional get() { - return Optional.empty(); - } - - @Override - public Optional getAsArray() { - return Optional.empty(); - } - - @Override - public void close() { - - } - }; - - private LdapContext ldapContext; - - public LdapMapContextManager(KeycloakSession session, LdapMapConfig connectionProperties) { - this.session = session; - this.ldapMapConfig = connectionProperties; - } - - public static LdapMapContextManager create(KeycloakSession session, LdapMapConfig connectionProperties) { - return new LdapMapContextManager(session, connectionProperties); - } - - private void createLdapContext() throws NamingException { - Hashtable connProp = getConnectionProperties(ldapMapConfig); - - if (!LDAPConstants.AUTH_TYPE_NONE.equals(ldapMapConfig.getAuthType())) { - vaultCharSecret = getVaultSecret(); - - if (vaultCharSecret != null && !ldapMapConfig.isStartTls()) { - connProp.put(SECURITY_CREDENTIALS, vaultCharSecret.getAsArray() - .orElse(ldapMapConfig.getBindCredential().toCharArray())); - } - } - - ldapContext = new InitialLdapContext(connProp, null); - if (ldapMapConfig.isStartTls()) { - SSLSocketFactory sslSocketFactory = null; - if (LdapMapUtil.shouldUseTruststoreSpi(ldapMapConfig)) { - TruststoreProvider provider = session.getProvider(TruststoreProvider.class); - sslSocketFactory = provider.getSSLSocketFactory(); - } - - tlsResponse = startTLS(ldapContext, ldapMapConfig.getAuthType(), ldapMapConfig.getBindDN(), - vaultCharSecret.getAsArray().orElse(ldapMapConfig.getBindCredential().toCharArray()), sslSocketFactory); - - // Exception should be already thrown by LDAPContextManager.startTLS if "startTLS" could not be established, but rather do some additional check - if (tlsResponse == null) { - throw new NamingException("Wasn't able to establish LDAP connection through StartTLS"); - } - } - } - - public LdapContext getLdapContext() throws NamingException { - if (ldapContext == null) createLdapContext(); - - return ldapContext; - } - - private VaultCharSecret getVaultSecret() { - return LDAPConstants.AUTH_TYPE_NONE.equals(ldapMapConfig.getAuthType()) - ? null - : session.vault().getCharSecret(ldapMapConfig.getBindCredential()); - } - - public static StartTlsResponse startTLS(LdapContext ldapContext, String authType, String bindDN, char[] bindCredential, SSLSocketFactory sslSocketFactory) throws NamingException { - StartTlsResponse tls; - - try { - tls = (StartTlsResponse) ldapContext.extendedOperation(new StartTlsRequest()); - tls.negotiate(sslSocketFactory); - - ldapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, authType); - - if (!LDAPConstants.AUTH_TYPE_NONE.equals(authType)) { - ldapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, bindDN); - ldapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, bindCredential); - } - } catch (Exception e) { - logger.error("Could not negotiate TLS", e); - throw new AuthenticationException("Could not negotiate TLS"); - } - - // throws AuthenticationException when authentication fails - ldapContext.lookup(""); - - return tls; - } - - // Get connection properties of admin connection - private Hashtable getConnectionProperties(LdapMapConfig ldapMapConfig) { - Hashtable env = getNonAuthConnectionProperties(ldapMapConfig); - - if(!ldapMapConfig.isStartTls()) { - String authType = ldapMapConfig.getAuthType(); - - env.put(Context.SECURITY_AUTHENTICATION, authType); - - String bindDN = ldapMapConfig.getBindDN(); - - char[] bindCredential = null; - - if (ldapMapConfig.getBindCredential() != null) { - bindCredential = ldapMapConfig.getBindCredential().toCharArray(); - } - - if (!LDAPConstants.AUTH_TYPE_NONE.equals(authType)) { - env.put(Context.SECURITY_PRINCIPAL, bindDN); - env.put(Context.SECURITY_CREDENTIALS, bindCredential); - } - } - - if (logger.isTraceEnabled()) { - Map copyEnv = new Hashtable<>(env); - if (copyEnv.containsKey(Context.SECURITY_CREDENTIALS)) { - copyEnv.put(Context.SECURITY_CREDENTIALS, "**************************************"); - } - logger.tracef("Creating LdapContext using properties: [%s]", copyEnv); - } - - return env; - } - - - /** - * This method is used for admin connection and user authentication. Hence it returns just connection properties NOT related to - * authentication (properties like bindType, bindDn, bindPassword). Caller of this method needs to fill auth-related connection properties - * based on the fact whether he does admin connection or user authentication - * - */ - public static Hashtable getNonAuthConnectionProperties(LdapMapConfig ldapMapConfig) { - HashMap env = new HashMap<>(); - - env.put(Context.INITIAL_CONTEXT_FACTORY, ldapMapConfig.getFactoryName()); - - String url = ldapMapConfig.getConnectionUrl(); - - if (url != null) { - env.put(Context.PROVIDER_URL, url); - } else { - logger.warn("LDAP URL is null. LDAPOperationManager won't work correctly"); - } - - // when using Start TLS, use default socket factory for LDAP client but pass the TrustStore SSL socket factory later - // when calling StartTlsResponse.negotiate(trustStoreSSLSocketFactory) - if (LdapMapUtil.shouldUseTruststoreSpi(ldapMapConfig)) { - env.put("java.naming.ldap.factory.socket", "org.keycloak.truststore.SSLSocketFactory"); - } - - String connectionPooling = ldapMapConfig.getConnectionPooling(); - if (connectionPooling != null) { - env.put("com.sun.jndi.ldap.connect.pool", connectionPooling); - } - - String connectionTimeout = ldapMapConfig.getConnectionTimeout(); - if (connectionTimeout != null && !connectionTimeout.isEmpty()) { - env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout); - } - - String readTimeout = ldapMapConfig.getReadTimeout(); - if (readTimeout != null && !readTimeout.isEmpty()) { - env.put("com.sun.jndi.ldap.read.timeout", readTimeout); - } - - // Just dump the additional properties - Properties additionalProperties = ldapMapConfig.getAdditionalConnectionProperties(); - if (additionalProperties != null) { - for (Object key : additionalProperties.keySet()) { - env.put(key.toString(), additionalProperties.getProperty(key.toString())); - } - } - - StringBuilder binaryAttrsBuilder = new StringBuilder(); - if (ldapMapConfig.isObjectGUID()) { - binaryAttrsBuilder.append(LDAPConstants.OBJECT_GUID).append(" "); - } - if (ldapMapConfig.isEdirectory()) { - binaryAttrsBuilder.append(LDAPConstants.NOVELL_EDIRECTORY_GUID).append(" "); - } - for (String attrName : ldapMapConfig.getBinaryAttributeNames()) { - binaryAttrsBuilder.append(attrName).append(" "); - } - - String binaryAttrs = binaryAttrsBuilder.toString().trim(); - if (!binaryAttrs.isEmpty()) { - env.put("java.naming.ldap.attributes.binary", binaryAttrs); - } - - return new Hashtable<>(env); - } - - @Override - public void close() { - if (vaultCharSecret != null) vaultCharSecret.close(); - if (tlsResponse != null) { - try { - tlsResponse.close(); - } catch (IOException e) { - logger.error("Could not close Ldap tlsResponse.", e); - } - } - - if (ldapContext != null) { - try { - ldapContext.close(); - } catch (NamingException e) { - logger.error("Could not close Ldap context.", e); - } - } - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapEscapeStrategy.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapEscapeStrategy.java deleted file mode 100644 index 36468aadd50..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapEscapeStrategy.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import java.nio.charset.StandardCharsets; - -/** - * @author Marek Posolda - */ -public enum LdapMapEscapeStrategy { - - - // LDAP special characters like * ( ) \ are not escaped. Only non-ASCII characters like é are escaped - NON_ASCII_CHARS_ONLY { - - @Override - public String escape(String input) { - StringBuilder output = new StringBuilder(); - - for (byte b : input.getBytes(StandardCharsets.UTF_8)) { - appendByte(b, output); - } - - return output.toString(); - } - - }, - - - // Escaping of LDAP special characters including non-ASCII characters like é - DEFAULT { - - - @Override - public String escape(String input) { - StringBuilder output = new StringBuilder(); - - for (byte b : input.getBytes(StandardCharsets.UTF_8)) { - switch (b) { - case 0x5c: - output.append("\\5c"); // \ - break; - case 0x2a: - output.append("\\2a"); // * - break; - case 0x28: - output.append("\\28"); // ( - break; - case 0x29: - output.append("\\29"); // ) - break; - case 0x00: - output.append("\\00"); // \u0000 - break; - default: { - appendByte(b, output); - } - } - } - - return output.toString(); - } - - }, - - // Escaping value as Octet-String - OCTET_STRING { - @Override - public String escape(String input) { - byte[] bytes; - bytes = input.getBytes(StandardCharsets.UTF_8); - return escapeHex(bytes); - } - - }; - - public static String escapeHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("\\%02x", b)); - } - return sb.toString(); - } - - public abstract String escape(String input); - - protected void appendByte(byte b, StringBuilder output) { - if (b >= 0) { - output.append((char) b); - } else { - int i = -256 ^ b; - output.append("\\").append(Integer.toHexString(i)); - } - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapIdentityStore.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapIdentityStore.java deleted file mode 100644 index 2ad1cb137f7..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapIdentityStore.java +++ /dev/null @@ -1,538 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Base64; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; -import org.keycloak.models.map.storage.ldap.model.LdapMapDn; -import org.keycloak.models.map.storage.ldap.model.LdapMapObject; -import org.keycloak.models.map.storage.ldap.model.LdapMapQuery; -import org.keycloak.representations.idm.LDAPCapabilityRepresentation; -import org.keycloak.representations.idm.LDAPCapabilityRepresentation.CapabilityType; - -import javax.naming.AuthenticationException; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.AttributeInUseException; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.ModificationItem; -import javax.naming.directory.NoSuchAttributeException; -import javax.naming.directory.SchemaViolationException; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * An IdentityStore implementation backed by an LDAP directory - * - * @author Shane Bryzak - * @author Anil Saldhana - * @author Pedro Silva - */ -public class LdapMapIdentityStore implements AutoCloseable { - - private static final Logger logger = Logger.getLogger(LdapMapIdentityStore.class); - private static final Pattern rangePattern = Pattern.compile("([^;]+);range=([0-9]+)-([0-9]+|\\*)"); - - private final LdapMapConfig config; - private final LdapMapOperationManager operationManager; - - public LdapMapIdentityStore(KeycloakSession session, LdapMapConfig config) { - this.config = config; - this.operationManager = new LdapMapOperationManager(session, config); - } - - public LdapMapConfig getConfig() { - return this.config; - } - - public void add(LdapMapObject ldapObject) { - // id will be assigned by the ldap server - if (ldapObject.getId() != null) { - throw new ModelException("Can't add object with already assigned uuid"); - } - - String entryDN = ldapObject.getDn().toString(); - BasicAttributes ldapAttributes = extractAttributesForSaving(ldapObject, true); - this.operationManager.createSubContext(entryDN, ldapAttributes); - ldapObject.setId(getEntryIdentifier(ldapObject)); - - if (logger.isDebugEnabled()) { - logger.debugf("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getId(), entryDN); - } - } - - public void addMemberToGroup(String groupDn, String memberAttrName, String value) { - // do not check EMPTY_MEMBER_ATTRIBUTE_VALUE, we save one useless query - // the value will be there forever for objectclasses that enforces the attribute as MUST - BasicAttribute attr = new BasicAttribute(memberAttrName, value); - ModificationItem item = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); - try { - this.operationManager.modifyAttributesNaming(groupDn, new ModificationItem[]{item}, null); - } catch (AttributeInUseException e) { - logger.debugf("Group %s already contains the member %s", groupDn, value); - } catch (NamingException e) { - throw new ModelException("Could not modify attribute for DN [" + groupDn + "]", e); - } - } - - public void removeMemberFromGroup(String groupDn, String memberAttrName, String value) { - BasicAttribute attr = new BasicAttribute(memberAttrName, value); - ModificationItem item = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr); - try { - this.operationManager.modifyAttributesNaming(groupDn, new ModificationItem[]{item}, null); - } catch (NoSuchAttributeException e) { - logger.debugf("Group %s does not contain the member %s", groupDn, value); - } catch (SchemaViolationException e) { - // schema violation removing one member => add the empty attribute, it cannot be other thing - logger.infof("Schema violation in group %s removing member %s. Trying adding empty member attribute.", groupDn, value); - try { - this.operationManager.modifyAttributesNaming(groupDn, - new ModificationItem[]{item, new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(memberAttrName, LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE))}, - null); - } catch (NamingException ex) { - throw new ModelException("Could not modify attribute for DN [" + groupDn + "]", ex); - } - } catch (NamingException e) { - throw new ModelException("Could not modify attribute for DN [" + groupDn + "]", e); - } - } - - public void update(LdapMapObject ldapObject) { - checkRename(ldapObject); - - BasicAttributes updatedAttributes = extractAttributesForSaving(ldapObject, false); - NamingEnumeration attributes = updatedAttributes.getAll(); - - String entryDn = ldapObject.getDn().toString(); - this.operationManager.modifyAttributes(entryDn, attributes); - - if (logger.isDebugEnabled()) { - logger.debugf("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getId(), entryDn); - } - } - - protected void checkRename(LdapMapObject ldapObject) { - LdapMapDn.RDN firstRdn = ldapObject.getDn().getFirstRdn(); - String oldDn = ldapObject.getDn().toString(); - - // Detect which keys will need to be updated in RDN, which are new keys to be added, and which are to be removed - List toUpdateKeys = firstRdn.getAllKeys(); - toUpdateKeys.retainAll(ldapObject.getRdnAttributeNames()); - - List toRemoveKeys = firstRdn.getAllKeys(); - toRemoveKeys.removeAll(ldapObject.getRdnAttributeNames()); - - List toAddKeys = new ArrayList<>(ldapObject.getRdnAttributeNames()); - toAddKeys.removeAll(firstRdn.getAllKeys()); - - // Go through all the keys in the oldRDN and doublecheck if they are changed or not - boolean changed = false; - for (String attrKey : toUpdateKeys) { - if (ldapObject.getReadOnlyAttributeNames().contains(attrKey.toLowerCase())) { - continue; - } - - String rdnAttrVal = ldapObject.getAttributeAsString(attrKey); - - // Could be the case when RDN attribute of the target object is not included in Keycloak mappers - if (rdnAttrVal == null) { - continue; - } - - String oldRdnAttrVal = firstRdn.getAttrValue(attrKey); - - if (!oldRdnAttrVal.equalsIgnoreCase(rdnAttrVal)) { - changed = true; - firstRdn.setAttrValue(attrKey, rdnAttrVal); - } - } - - // Add new keys - for (String attrKey : toAddKeys) { - String rdnAttrVal = ldapObject.getAttributeAsString(attrKey); - - // Could be the case when RDN attribute of the target object is not included in Keycloak mappers - if (rdnAttrVal == null) { - continue; - } - - changed = true; - firstRdn.setAttrValue(attrKey, rdnAttrVal); - } - - // Remove old keys - for (String attrKey : toRemoveKeys) { - changed |= firstRdn.removeAttrValue(attrKey); - } - - if (changed) { - LdapMapDn newLdapMapDn = ldapObject.getDn().getParentDn(); - newLdapMapDn.addFirst(firstRdn); - - String newDn = newLdapMapDn.toString(); - - logger.debugf("Renaming LDAP Object. Old DN: [%s], New DN: [%s]", oldDn, newDn); - - // In case, that there is conflict (For example already existing "CN=John Anthony"), the different DN is returned - newDn = this.operationManager.renameEntry(oldDn, newDn, true); - - ldapObject.setDn(LdapMapDn.fromString(newDn)); - } - } - - public void remove(LdapMapObject ldapObject) { - this.operationManager.removeEntry(ldapObject.getDn().toString()); - - if (logger.isDebugEnabled()) { - logger.debugf("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getId(), ldapObject.getDn().toString()); - } - } - - public LdapMapObject fetchById(String id, LdapMapQuery identityQuery) { - SearchResult search = this.operationManager - .lookupById(identityQuery.getSearchDn(), id, identityQuery.getReturningLdapAttributes()); - - if (search != null) { - return populateAttributedType(search, identityQuery); - } else { - return null; - } - } - - public List fetchQueryResults(LdapMapQuery identityQuery) { - List results = new ArrayList<>(); - - StringBuilder filter = null; - - try { - String baseDN = identityQuery.getSearchDn(); - - filter = createIdentityTypeSearchFilter(identityQuery); - - List search; - search = this.operationManager.search(baseDN, filter.toString(), identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope()); - - for (SearchResult result : search) { - // don't add the branch in subtree search - if (identityQuery.getSearchScope() != SearchControls.SUBTREE_SCOPE || !result.getNameInNamespace().equalsIgnoreCase(baseDN)) { - results.add(populateAttributedType(result, identityQuery)); - } - } - } catch (Exception e) { - throw new ModelException("Querying of LDAP failed " + identityQuery + ", filter: " + filter, e); - } - - return results; - } - - public Set queryServerCapabilities() { - Set result = new LinkedHashSet<>(); - try { - List attrs = new ArrayList<>(); - attrs.add("supportedControl"); - attrs.add("supportedExtension"); - attrs.add("supportedFeatures"); - List searchResults = operationManager - .search("", "(objectClass=*)", Collections.unmodifiableCollection(attrs), SearchControls.OBJECT_SCOPE); - if (searchResults.size() != 1) { - throw new ModelException("Could not query root DSE: unexpected result size"); - } - SearchResult rootDse = searchResults.get(0); - Attributes attributes = rootDse.getAttributes(); - for (String attr: attrs) { - Attribute attribute = attributes.get(attr); - if (null != attribute) { - CapabilityType capabilityType = CapabilityType.fromRootDseAttributeName(attr); - NamingEnumeration values = attribute.getAll(); - while (values.hasMoreElements()) { - Object o = values.nextElement(); - LDAPCapabilityRepresentation capability = new LDAPCapabilityRepresentation(o, capabilityType); - logger.info("rootDSE query: " + capability); - result.add(capability); - } - } - } - return result; - } catch (NamingException e) { - throw new ModelException("Failed to query root DSE: " + e.getMessage(), e); - } - } - - // *************** CREDENTIALS AND USER SPECIFIC STUFF - - public void validatePassword(LdapMapObject user, String password) throws AuthenticationException { - String userDN = user.getDn().toString(); - - if (logger.isTraceEnabled()) { - logger.tracef("Using DN [%s] for authentication of user", userDN); - } - - operationManager.authenticate(userDN, password); - } - - // ************ END CREDENTIALS AND USER SPECIFIC STUFF - - protected StringBuilder createIdentityTypeSearchFilter(final LdapMapQuery identityQuery) { - StringBuilder filter = identityQuery.getModelCriteriaBuilder().getPredicateFunc().get(); - - filter.insert(0, "(&"); - filter.append(getObjectClassesFilter(identityQuery.getObjectClasses())); - filter.append(")"); - - if (logger.isTraceEnabled()) { - logger.tracef("Using filter for LDAP search: %s . Searching in DN: %s", filter, identityQuery.getSearchDn()); - } - return filter; - } - - - private StringBuilder getObjectClassesFilter(Collection objectClasses) { - StringBuilder builder = new StringBuilder(); - - if (!objectClasses.isEmpty()) { - for (String objectClass : objectClasses) { - builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")"); - } - } else { - builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append("*").append(")"); - } - - return builder; - } - - - private LdapMapObject populateAttributedType(SearchResult searchResult, LdapMapQuery ldapQuery) { - Set readOnlyAttrNames = ldapQuery.getReturningReadOnlyLdapAttributes(); - Set lowerCasedAttrNames = new TreeSet<>(); - for (String attrName : ldapQuery.getReturningLdapAttributes()) { - lowerCasedAttrNames.add(attrName.toLowerCase()); - } - - try { - String entryDN = searchResult.getNameInNamespace(); - Attributes attributes = searchResult.getAttributes(); - - LdapMapObject ldapObject = new LdapMapObject(); - LdapMapDn dn = LdapMapDn.fromString(entryDN); - ldapObject.setDn(dn); - ldapObject.setRdnAttributeNames(dn.getFirstRdn().getAllKeys()); - - NamingEnumeration ldapAttributes = attributes.getAll(); - - while (ldapAttributes.hasMore()) { - Attribute ldapAttribute = ldapAttributes.next(); - - try { - ldapAttribute.get(); - } catch (NoSuchElementException nsee) { - continue; - } - - String ldapAttributeName = ldapAttribute.getID(); - - // check for ranged attribute - Matcher m = rangePattern.matcher(ldapAttributeName); - if (m.matches()) { - ldapAttributeName = m.group(1); - // range=X-* means all the attributes returned - if (!m.group(3).equals("*")) { - try { - int max = Integer.parseInt(m.group(3)); - ldapObject.addRangedAttribute(ldapAttributeName, max); - } catch (NumberFormatException e) { - logger.warnf("Invalid ranged expresion for attribute: %s", m.group(0)); - } - } - } - - if (ldapAttributeName.equalsIgnoreCase(getConfig().getUuidLDAPAttributeName())) { - Object uuidValue = ldapAttribute.get(); - ldapObject.setId(this.operationManager.decodeEntryUUID(uuidValue)); - } - - // Note: UUID is normally not populated here. It's populated just in case that it's used for name of other attribute as well - if (!ldapAttributeName.equalsIgnoreCase(getConfig().getUuidLDAPAttributeName()) || (lowerCasedAttrNames.contains(ldapAttributeName.toLowerCase()))) { - Set attrValues = new LinkedHashSet<>(); - NamingEnumeration enumm = ldapAttribute.getAll(); - while (enumm.hasMoreElements()) { - Object val = enumm.next(); - - if (val instanceof byte[]) { // byte[] - String attrVal = Base64.encodeBytes((byte[]) val); - attrValues.add(attrVal); - } else { // String - String attrVal = val.toString().trim(); - attrValues.add(attrVal); - } - } - - if (ldapAttributeName.equalsIgnoreCase(LDAPConstants.OBJECT_CLASS)) { - ldapObject.setObjectClasses(attrValues); - } else { - ldapObject.setAttribute(ldapAttributeName, attrValues); - - // readOnlyAttrNames are lower-cased - if (readOnlyAttrNames.contains(ldapAttributeName.toLowerCase())) { - ldapObject.addReadOnlyAttributeName(ldapAttributeName); - } - } - } - } - - if (logger.isTraceEnabled()) { - logger.tracef("Found ldap object and populated with the attributes. LDAP Object: %s", ldapObject.toString()); - } - return ldapObject; - - } catch (Exception e) { - throw new ModelException("Could not populate attribute type " + searchResult.getNameInNamespace() + ".", e); - } - } - - - protected BasicAttributes extractAttributesForSaving(LdapMapObject ldapObject, boolean isCreate) { - BasicAttributes entryAttributes = new BasicAttributes(); - - Set rdnAttrNamesLowerCased = ldapObject.getRdnAttributeNames().stream() - .map(String::toLowerCase) - .collect(Collectors.toSet()); - - for (Map.Entry> attrEntry : ldapObject.getAttributes().entrySet()) { - String attrName = attrEntry.getKey(); - Set attrValue = attrEntry.getValue(); - - if (attrValue == null) { - // Shouldn't happen - logger.warnf("Attribute '%s' is null on LDAP object '%s' . Using empty value to be saved to LDAP", attrName, ldapObject.getDn().toString()); - attrValue = Collections.emptySet(); - } - - String attrNameLowercased = attrName.toLowerCase(); - if ( - // Ignore empty attributes on create (changetype: add) - !(isCreate && attrValue.isEmpty()) && - - // Since we're extracting for saving, skip read-only attributes. ldapObject.getReadOnlyAttributeNames() are lower-cased - !ldapObject.getReadOnlyAttributeNames().contains(attrNameLowercased) && - - // Only extract RDN for create since it can't be changed on update - (isCreate || !rdnAttrNamesLowerCased.contains(attrNameLowercased)) - ) { - if (getConfig().getBinaryAttributeNames().contains(attrName)) { - // Binary attribute - entryAttributes.put(createBinaryBasicAttribute(attrName, attrValue)); - } else { - // Text attribute - entryAttributes.put(createBasicAttribute(attrName, attrValue)); - } - } - } - - // Don't extract object classes for update - if (isCreate) { - BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS); - - for (String objectClassValue : ldapObject.getObjectClasses()) { - objectClassAttribute.add(objectClassValue); - } - - entryAttributes.put(objectClassAttribute); - } - - return entryAttributes; - } - - private BasicAttribute createBasicAttribute(String attrName, Set attrValue) { - BasicAttribute attr = new BasicAttribute(attrName); - - for (String value : attrValue) { - if (value == null || value.trim().length() == 0) { - value = LDAPConstants.EMPTY_ATTRIBUTE_VALUE; - } - - attr.add(value); - } - - return attr; - } - - private BasicAttribute createBinaryBasicAttribute(String attrName, Set attrValue) { - BasicAttribute attr = new BasicAttribute(attrName); - - for (String value : attrValue) { - if (value == null || value.trim().length() == 0) { - value = LDAPConstants.EMPTY_ATTRIBUTE_VALUE; - } - - try { - byte[] bytes = Base64.decode(value); - attr.add(bytes); - } catch (IOException ioe) { - logger.warnf("Wasn't able to Base64 decode the attribute value. Ignoring attribute update. Attribute: %s, Attribute value: %s", attrName, attrValue); - } - } - - return attr; - } - - protected String getEntryIdentifier(final LdapMapObject ldapObject) { - try { - // we need this to retrieve the entry's identifier from the ldap server - String uuidAttrName = getConfig().getUuidLDAPAttributeName(); - - String rdn = ldapObject.getDn().getFirstRdn().toString(false); - String filter = "(" + LdapMapEscapeStrategy.DEFAULT.escape(rdn) + ")"; - List search = this.operationManager.search(ldapObject.getDn().toString(), filter, Collections.singletonList(uuidAttrName), SearchControls.OBJECT_SCOPE); - Attribute id = search.get(0).getAttributes().get(getConfig().getUuidLDAPAttributeName()); - - if (id == null) { - throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "]."); - } - - return this.operationManager.decodeEntryUUID(id.get()); - } catch (NamingException ne) { - throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "]."); - } - } - - @Override - public void close() { - operationManager.close(); - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOctetStringEncoder.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOctetStringEncoder.java deleted file mode 100644 index 9176fc8105a..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOctetStringEncoder.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -public class LdapMapOctetStringEncoder { - - private final LdapMapEscapeStrategy fallback; - - public LdapMapOctetStringEncoder() { - this(null); - } - - public LdapMapOctetStringEncoder(LdapMapEscapeStrategy fallback) { - this.fallback = fallback; - } - - - public String encode(Object parameterValue, boolean isBinary) { - String escaped; - if (parameterValue instanceof byte[]) { - escaped = LdapMapEscapeStrategy.escapeHex((byte[]) parameterValue); - } else { - escaped = escapeAsString(parameterValue, isBinary); - } - return escaped; - } - - private String escapeAsString(Object parameterValue, boolean isBinary) { - String escaped; - String stringValue = parameterValue.toString(); - if (isBinary) { - escaped = LdapMapEscapeStrategy.OCTET_STRING.escape(stringValue); - } else if (fallback == null){ - escaped = stringValue; - } else { - escaped = fallback.escape(stringValue); - } - return escaped; - } - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOperationDecorator.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOperationDecorator.java deleted file mode 100644 index 1a2891bf1af..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOperationDecorator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import javax.naming.NamingException; -import javax.naming.ldap.LdapContext; - -/** - * @author Marek Posolda - */ -public interface LdapMapOperationDecorator { - - void beforeLDAPOperation(LdapContext ldapContext, LdapMapOperationManager.LdapOperation ldapOperation) throws NamingException; - -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOperationManager.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOperationManager.java deleted file mode 100644 index 7f59c9e6df4..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapOperationManager.java +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; -import org.keycloak.models.map.storage.ldap.model.LdapMapDn; -import org.keycloak.truststore.TruststoreProvider; - -import javax.naming.AuthenticationException; -import javax.naming.Binding; -import javax.naming.Context; -import javax.naming.NameAlreadyBoundException; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.ModificationItem; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.InitialLdapContext; -import javax.naming.ldap.LdapContext; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.StartTlsResponse; -import javax.net.ssl.SSLSocketFactory; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.List; -import java.util.Set; - -/** - *

This class provides a set of operations to manage LDAP trees.

- * - * @author Anil Saldhana - * @author Pedro Silva - */ -public class LdapMapOperationManager implements AutoCloseable { - - private static final Logger logger = Logger.getLogger(LdapMapOperationManager.class); - - private static final Logger perfLogger = Logger.getLogger(LdapMapOperationManager.class, "perf"); - - private final KeycloakSession session; - private final LdapMapConfig config; - private LdapMapContextManager ldapMapContextManager; - - public LdapMapOperationManager(KeycloakSession session, LdapMapConfig config) { - this.session = session; - this.config = config; - } - - /** - *

- * Modifies the given {@link Attribute} instance using the given DN. This method performs a REPLACE_ATTRIBUTE - * operation. - *

- * - */ - public void modifyAttribute(String dn, Attribute attribute) { - ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)}; - modifyAttributes(dn, mods, null); - } - - /** - *

- * Modifies the given {@link Attribute} instances using the given DN. This method performs a REPLACE_ATTRIBUTE - * operation. - *

- * - */ - public void modifyAttributes(String dn, NamingEnumeration attributes) { - try { - List modItems = new ArrayList<>(); - while (attributes.hasMore()) { - ModificationItem modItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attributes.next()); - modItems.add(modItem); - } - - modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}), null); - } catch (NamingException ne) { - throw new ModelException("Could not modify attributes on entry from DN [" + dn + "]", ne); - } - - } - - /** - *

- * Removes the given {@link Attribute} instance using the given DN. This method performs a REMOVE_ATTRIBUTE - * operation. - *

- * - */ - public void removeAttribute(String dn, Attribute attribute) { - ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)}; - modifyAttributes(dn, mods, null); - } - - /** - *

- * Adds the given {@link Attribute} instance using the given DN. This method performs a ADD_ATTRIBUTE operation. - *

- * - */ - public void addAttribute(String dn, Attribute attribute) { - ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)}; - modifyAttributes(dn, mods, null); - } - - /** - *

- * Removes the object from the LDAP tree - *

- */ - public void removeEntry(final String entryDn) { - try { - execute(new LdapOperation() { - - @Override - public SearchResult execute(LdapContext context) { - if (logger.isTraceEnabled()) { - logger.tracef("Removing entry with DN [%s]", entryDn); - } - destroySubcontext(context, entryDn); - return null; - } - - - @Override - public String toString() { - return "LdapOperation: remove\n" + - " dn: " + entryDn; - } - - }); - } catch (NamingException e) { - throw new ModelException("Could not remove entry from DN [" + entryDn + "]", e); - } - } - - - /** - * Rename LDAPObject name (DN) - * - * @param fallback With fallback=true, we will try to find the another DN in case of conflict. For example if there is an - * attempt to rename to "CN=John Doe", but there is already existing "CN=John Doe", we will try "CN=John Doe0" - * @return the non-conflicting DN, which was used in the end - */ - public String renameEntry(String oldDn, String newDn, boolean fallback) { - try { - return execute(new LdapOperation() { - - @Override - public String execute(LdapContext context) throws NamingException { - String dn = newDn; - - // Max 5 attempts for now - int max = 5; - for (int i=0 ; i search(final String baseDN, final String filter, Collection returningAttributes, int searchScope) throws NamingException { - final List result = new ArrayList<>(); - final SearchControls cons = getSearchControls(returningAttributes, searchScope); - - return execute(new LdapOperation>() { - @Override - public List execute(LdapContext context) throws NamingException { - NamingEnumeration search = context.search(new LdapName(baseDN), filter, cons); - - while (search.hasMoreElements()) { - result.add(search.nextElement()); - } - - search.close(); - - return result; - } - - - @Override - public String toString() { - return "LdapOperation: search\n" + - " baseDn: " + baseDN + "\n" + - " filter: " + filter + "\n" + - " searchScope: " + searchScope + "\n" + - " returningAttrs: " + returningAttributes + "\n" + - " resultSize: " + result.size(); - } - - - }); - } - - private SearchControls getSearchControls(Collection returningAttributes, int searchScope) { - final SearchControls cons = new SearchControls(); - - cons.setSearchScope(searchScope); - cons.setReturningObjFlag(false); - - returningAttributes = getReturningAttributes(returningAttributes); - - cons.setReturningAttributes(returningAttributes.toArray(new String[0])); - return cons; - } - - public String getFilterById(String id) { - StringBuilder filter = new StringBuilder(); - filter.insert(0, "(&"); - - if (this.config.isObjectGUID()) { - byte[] objectGUID = LdapMapUtil.encodeObjectGUID(id); - filter.append("(objectClass=*)(").append( - getUuidAttributeName()).append(LDAPConstants.EQUAL) - .append(LdapMapUtil.convertObjectGUIDToByteString( - objectGUID)).append(")"); - - } else if (this.config.isEdirectoryGUID()) { - filter.append("(objectClass=*)(").append(getUuidAttributeName().toUpperCase()) - .append(LDAPConstants.EQUAL - ).append(LdapMapUtil.convertGUIDToEdirectoryHexString(id)).append(")"); - } else { - filter.append("(objectClass=*)(").append(getUuidAttributeName()).append(LDAPConstants.EQUAL) - .append(id).append(")"); - } - - if (config.getCustomUserSearchFilter() != null) { - filter.append(config.getCustomUserSearchFilter()); - } - - filter.append(")"); - String ldapIdFilter = filter.toString(); - - logger.tracef("Using filter for lookup user by LDAP ID: %s", ldapIdFilter); - - return ldapIdFilter; - } - - public SearchResult lookupById(final String baseDN, final String id, final Collection returningAttributes) { - final String filter = getFilterById(id); - - try { - final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope()); - - return execute(new LdapOperation() { - - @Override - public SearchResult execute(LdapContext context) throws NamingException { - NamingEnumeration search = context.search(new LdapName(baseDN), filter, cons); - - try { - if (search.hasMoreElements()) { - return search.next(); - } - } finally { - if (search != null) { - search.close(); - } - } - - return null; - } - - - @Override - public String toString() { - return "LdapOperation: lookupById\n" + - " baseDN: " + baseDN + "\n" + - " filter: " + filter + "\n" + - " searchScope: " + cons.getSearchScope() + "\n" + - " returningAttrs: " + returningAttributes; - } - - }); - } catch (NamingException e) { - throw new ModelException("Could not query server using DN [" + baseDN + "] and filter [" + filter + "]", e); - } - } - - /** - *

- * Destroys a subcontext with the given DN from the LDAP tree. - *

- */ - private void destroySubcontext(LdapContext context, final String dn) { - try { - NamingEnumeration enumeration = null; - - try { - enumeration = context.listBindings(new LdapName(dn)); - - while (enumeration.hasMore()) { - Binding binding = enumeration.next(); - String name = binding.getNameInNamespace(); - - destroySubcontext(context, name); - } - - context.unbind(new LdapName(dn)); - } finally { - if (enumeration != null) { - try { - enumeration.close(); - } catch (Exception e) { - logger.warn("problem during close", e); - } - } - } - } catch (Exception e) { - throw new ModelException("Could not unbind DN [" + dn + "]", e); - } - } - - /** - *

- * Performs a simple authentication using the given DN and password to bind to the authentication context. - *

- * - * @throws AuthenticationException if authentication is not successful - * - */ - public void authenticate(String dn, String password) throws AuthenticationException { - - if (password == null || password.isEmpty()) { - throw new AuthenticationException("Empty password used"); - } - - LdapContext authCtx = null; - StartTlsResponse tlsResponse = null; - - try { - Hashtable env = LdapMapContextManager.getNonAuthConnectionProperties(config); - - // Never use connection pool to prevent password caching - env.put("com.sun.jndi.ldap.connect.pool", "false"); - - if(!this.config.isStartTls()) { - env.put(Context.SECURITY_AUTHENTICATION, "simple"); - env.put(Context.SECURITY_PRINCIPAL, dn); - env.put(Context.SECURITY_CREDENTIALS, password); - } - - authCtx = new InitialLdapContext(env, null); - if (config.isStartTls()) { - SSLSocketFactory sslSocketFactory = null; - String useTruststoreSpi = config.getUseTruststoreSpi(); - if (useTruststoreSpi != null && useTruststoreSpi.equals(LDAPConstants.USE_TRUSTSTORE_ALWAYS)) { - TruststoreProvider provider = session.getProvider(TruststoreProvider.class); - sslSocketFactory = provider.getSSLSocketFactory(); - } - - tlsResponse = LdapMapContextManager.startTLS(authCtx, "simple", dn, password.toCharArray(), sslSocketFactory); - - // Exception should be already thrown by LDAPContextManager.startTLS if "startTLS" could not be established, but rather do some additional check - if (tlsResponse == null) { - throw new AuthenticationException("Null TLS Response returned from the authentication"); - } - } - } catch (AuthenticationException ae) { - if (logger.isDebugEnabled()) { - logger.debugf(ae, "Authentication failed for DN [%s]", dn); - } - - throw ae; - } catch(RuntimeException re){ - if (logger.isDebugEnabled()) { - logger.debugf(re, "LDAP Connection TimeOut for DN [%s]", dn); - } - - throw re; - - } catch (Exception e) { - logger.errorf(e, "Unexpected exception when validating password of DN [%s]", dn); - throw new AuthenticationException("Unexpected exception when validating password of user"); - } finally { - if (tlsResponse != null) { - try { - tlsResponse.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - if (authCtx != null) { - try { - authCtx.close(); - } catch (NamingException e) { - e.printStackTrace(); - } - } - } - } - - public void modifyAttributesNaming(final String dn, final ModificationItem[] mods, LdapMapOperationDecorator decorator) throws NamingException { - if (logger.isTraceEnabled()) { - logger.tracef("Modifying attributes for entry [%s]: [", dn); - - for (ModificationItem item : mods) { - Object values; - - if (item.getAttribute().size() > 0) { - values = item.getAttribute().get(); - } else { - values = "No values"; - } - - String attrName = item.getAttribute().getID().toUpperCase(); - if (attrName.contains("PASSWORD") || attrName.contains("UNICODEPWD")) { - values = "********************"; - } - - logger.tracef(" Op [%s]: %s = %s", item.getModificationOp(), item.getAttribute().getID(), values); - } - - logger.tracef("]"); - } - - execute(new LdapOperation() { - - @Override - public Void execute(LdapContext context) throws NamingException { - context.modifyAttributes(new LdapName(dn), mods); - return null; - } - - @Override - public String toString() { - return "LdapOperation: modify\n" + - " dn: " + dn + "\n" + - " modificationsSize: " + mods.length; - } - - }, decorator); - } - - public void modifyAttributes(final String dn, final ModificationItem[] mods, LdapMapOperationDecorator decorator) { - try { - modifyAttributesNaming(dn, mods, decorator); - } catch (NamingException e) { - throw new ModelException("Could not modify attribute for DN [" + dn + "]", e); - } - } - - public void createSubContext(final String name, final Attributes attributes) { - try { - if (logger.isTraceEnabled()) { - logger.tracef("Creating entry [%s] with attributes: [", name); - - NamingEnumeration all = attributes.getAll(); - - while (all.hasMore()) { - Attribute attribute = all.next(); - - String attrName = attribute.getID().toUpperCase(); - Object attrVal = attribute.get(); - if (attrName.contains("PASSWORD") || attrName.contains("UNICODEPWD")) { - attrVal = "********************"; - } - - logger.tracef(" %s = %s", attribute.getID(), attrVal); - } - - logger.tracef("]"); - } - - execute(new LdapOperation() { - @Override - public Void execute(LdapContext context) throws NamingException { - DirContext subcontext = context.createSubcontext(new LdapName(name), attributes); - - subcontext.close(); - - return null; - } - - - @Override - public String toString() { - return "LdapOperation: create\n" + - " dn: " + name + "\n" + - " attributesSize: " + attributes.size(); - } - - }); - } catch (NamingException e) { - throw new ModelException("Error creating subcontext [" + name + "]", e); - } - } - - private String getUuidAttributeName() { - return this.config.getUuidLDAPAttributeName(); - } - - public Attributes getAttributes(final String entryUUID, final String baseDN, Set returningAttributes) { - SearchResult search = lookupById(baseDN, entryUUID, returningAttributes); - - if (search == null) { - throw new ModelException("Couldn't find item with ID [" + entryUUID + " under base DN [" + baseDN + "]"); - } - - return search.getAttributes(); - } - - public String decodeEntryUUID(final Object entryUUID) { - if (entryUUID instanceof byte[]) { - if (this.config.isObjectGUID()) { - return LdapMapUtil.decodeObjectGUID((byte[]) entryUUID); - } - if (this.config.isEdirectory() && this.config.isEdirectoryGUID()) { - return LdapMapUtil.decodeGuid((byte[]) entryUUID); - } - } - return entryUUID.toString(); - } - - private R execute(LdapOperation operation) throws NamingException { - return execute(operation, null); - } - - private R execute(LdapOperation operation, LdapMapOperationDecorator decorator) throws NamingException { - return execute(operation, getLdapContextManager().getLdapContext(), decorator); - } - - private LdapMapContextManager getLdapContextManager() { - if (ldapMapContextManager == null) { - ldapMapContextManager = LdapMapContextManager.create(session, config); - } - return ldapMapContextManager; - } - - private R execute(LdapOperation operation, LdapContext context, LdapMapOperationDecorator decorator) throws NamingException { - if (context == null) { - throw new IllegalArgumentException("Ldap context cannot be null"); - } - - Long start = null; - - if (perfLogger.isDebugEnabled()) { - start = Time.currentTimeMillis(); - } - - try { - if (decorator != null) { - decorator.beforeLDAPOperation(context, operation); - } - - return operation.execute(context); - } finally { - if (start != null) { - long took = Time.currentTimeMillis() - start; - - if (took > 100) { - perfLogger.debugf("\n%s\ntook: %d ms\n", operation.toString(), took); - } else if (perfLogger.isTraceEnabled()) { - perfLogger.tracef("\n%s\ntook: %d ms\n", operation.toString(), took); - } - } - } - } - - @Override - public void close() { - ldapMapContextManager.close(); - } - - public interface LdapOperation { - R execute(LdapContext context) throws NamingException; - } - - private Set getReturningAttributes(final Collection returningAttributes) { - Set result = new HashSet<>(returningAttributes); - result.add(getUuidAttributeName()); - result.add(LDAPConstants.OBJECT_CLASS); - - return result; - } -} diff --git a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapUtil.java b/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapUtil.java deleted file mode 100644 index af3ca55af4c..00000000000 --- a/model/map-ldap/src/main/java/org/keycloak/models/map/storage/ldap/store/LdapMapUtil.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import org.jboss.logging.Logger; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.map.storage.ldap.config.LdapMapConfig; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -/** - *

Utility class for working with LDAP.

- * - * @author Pedro Igor - */ -public class LdapMapUtil { - - private static final Logger logger = Logger.getLogger(LdapMapUtil.class); - - /** - *

Formats the given date.

- * - * @param date The Date to format. - * - * @return A String representing the formatted date. - */ - public static String formatDate(Date date) { - if (date == null) { - throw new IllegalArgumentException("You must provide a date."); - } - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss'.0Z'"); - - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - - return dateFormat.format(date); - } - - /** - *

- * Parses dates/time stamps stored in LDAP. Some possible values: - *

- *
    - *
  • 20020228150820
  • - *
  • 20030228150820Z
  • - *
  • 20050228150820.12
  • - *
  • 20060711011740.0Z
  • - *
- * - * @param date The date string to parse from. - * - * @return the Date. - */ - public static Date parseDate(String date) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); - - try { - if (date.endsWith("Z")) { - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } else { - dateFormat.setTimeZone(TimeZone.getDefault()); - } - - return dateFormat.parse(date); - } catch (Exception e) { - throw new ModelException("Error converting ldap date.", e); - } - } - - - - /** - *

Creates a byte-based {@link String} representation of a raw byte array representing the value of the - * objectGUID attribute retrieved from Active Directory.

- * - *

The returned string is useful to perform queries on AD based on the objectGUID value. Eg.:

- * - *

- * String filter = "(&(objectClass=*)(objectGUID" + EQUAL + convertObjectGUIDToByteString(objectGUID) + "))"; - *

- * - * @param objectGUID A raw byte array representing the value of the objectGUID attribute retrieved from - * Active Directory. - * - * @return A byte-based String representation in the form of \[0]\[1]\[2]\[3]\[4]\[5]\[6]\[7]\[8]\[9]\[10]\[11]\[12]\[13]\[14]\[15] - */ - public static String convertObjectGUIDToByteString(byte[] objectGUID) { - StringBuilder result = new StringBuilder(); - - for (byte b : objectGUID) { - String transformed = prefixZeros((int) b & 0xFF); - result.append("\\"); - result.append(transformed); - } - - return result.toString(); - } - - /** - * see http://support.novell.com/docs/Tids/Solutions/10096551.html - * - * @param guid A GUID in the form of a dashed String as the result of (@see LDAPUtil#convertToDashedString) - * - * @return A String representation in the form of \[0][1]\[2][3]\[4][5]\[6][7]\[8][9]\[10][11]\[12][13]\[14][15] - */ - public static String convertGUIDToEdirectoryHexString(String guid) { - String withoutDash = guid.replace("-", ""); - StringBuilder result = new StringBuilder(); - - for (int i = 0; i < withoutDash.length(); i++) { - result.append("\\"); - result.append(withoutDash.charAt(i)); - result.append(withoutDash.charAt(++i)); - } - - return result.toString().toUpperCase(); - } - - /** - *

Encode a string representing the display value of the objectGUID attribute retrieved from Active - * Directory.

- * - * @param displayString A string representing the decoded value in the form of [3][2][1][0]-[5][4]-[7][6]-[8][9]-[10][11][12][13][14][15]. - * - * @return A raw byte array representing the value of the objectGUID attribute retrieved from - * Active Directory. - */ - public static byte[] encodeObjectGUID(String displayString) { - byte [] objectGUID = new byte[16]; - // [3][2][1][0] - objectGUID[0] = (byte) ((Character.digit(displayString.charAt(6), 16) << 4) - + Character.digit(displayString.charAt(7), 16)); - objectGUID[1] = (byte) ((Character.digit(displayString.charAt(4), 16) << 4) - + Character.digit(displayString.charAt(5), 16)); - objectGUID[2] = (byte) ((Character.digit(displayString.charAt(2), 16) << 4) - + Character.digit(displayString.charAt(3), 16)); - objectGUID[3] = (byte) ((Character.digit(displayString.charAt(0), 16) << 4) - + Character.digit(displayString.charAt(1), 16)); - // [5][4] - objectGUID[4] = (byte) ((Character.digit(displayString.charAt(11), 16) << 4) - + Character.digit(displayString.charAt(12), 16)); - objectGUID[5] = (byte) ((Character.digit(displayString.charAt(9), 16) << 4) - + Character.digit(displayString.charAt(10), 16)); - // [7][6] - objectGUID[6] = (byte) ((Character.digit(displayString.charAt(16), 16) << 4) - + Character.digit(displayString.charAt(17), 16)); - objectGUID[7] = (byte) ((Character.digit(displayString.charAt(14), 16) << 4) - + Character.digit(displayString.charAt(15), 16)); - // [8][9] - objectGUID[8] = (byte) ((Character.digit(displayString.charAt(19), 16) << 4) - + Character.digit(displayString.charAt(20), 16)); - objectGUID[9] = (byte) ((Character.digit(displayString.charAt(21), 16) << 4) - + Character.digit(displayString.charAt(22), 16)); - // [10][11][12][13][14][15] - objectGUID[10] = (byte) ((Character.digit(displayString.charAt(24), 16) << 4) - + Character.digit(displayString.charAt(25), 16)); - objectGUID[11] = (byte) ((Character.digit(displayString.charAt(26), 16) << 4) - + Character.digit(displayString.charAt(27), 16)); - objectGUID[12] = (byte) ((Character.digit(displayString.charAt(28), 16) << 4) - + Character.digit(displayString.charAt(29), 16)); - objectGUID[13] = (byte) ((Character.digit(displayString.charAt(30), 16) << 4) - + Character.digit(displayString.charAt(31), 16)); - objectGUID[14] = (byte) ((Character.digit(displayString.charAt(32), 16) << 4) - + Character.digit(displayString.charAt(33), 16)); - objectGUID[15] = (byte) ((Character.digit(displayString.charAt(34), 16) << 4) - + Character.digit(displayString.charAt(35), 16)); - return objectGUID; - } - - /** - *

Decode a raw byte array representing the value of the objectGUID attribute retrieved from Active - * Directory.

- * - *

The returned string is useful to directly bind an entry. Eg.:

- * - *

- * String bindingString = decodeObjectGUID(objectGUID); - *
- * Attributes attributes = ctx.getAttributes(bindingString); - *

- * - * @param objectGUID A raw byte array representing the value of the objectGUID attribute retrieved from - * Active Directory. - * - * @return A string representing the decoded value in the form of [3][2][1][0]-[5][4]-[7][6]-[8][9]-[10][11][12][13][14][15]. - */ - public static String decodeObjectGUID(byte[] objectGUID) { - return convertToDashedString(objectGUID); - } - - /** - *

Decode a raw byte array representing the value of the guid attribute retrieved from Novell - * eDirectory.

- * - * @param guid A raw byte array representing the value of the guid attribute retrieved from - * Novell eDirectory. - * - * @return A string representing the decoded value in the form of [0][1][2][3]-[4][5]-[6][7]-[8][9]-[10][11][12][13][14][15]. - */ - public static String decodeGuid(byte[] guid) { - byte[] withBigEndian = new byte[] { guid[3], guid[2], guid[1], guid[0], - guid[5], guid[4], - guid[7], guid[6], - guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15] - }; - return convertToDashedString(withBigEndian); - } - - private static String convertToDashedString(byte[] objectGUID) { - return prefixZeros((int) objectGUID[3] & 0xFF) + - prefixZeros((int) objectGUID[2] & 0xFF) + - prefixZeros((int) objectGUID[1] & 0xFF) + - prefixZeros((int) objectGUID[0] & 0xFF) + - "-" + - prefixZeros((int) objectGUID[5] & 0xFF) + - prefixZeros((int) objectGUID[4] & 0xFF) + - "-" + - prefixZeros((int) objectGUID[7] & 0xFF) + - prefixZeros((int) objectGUID[6] & 0xFF) + - "-" + - prefixZeros((int) objectGUID[8] & 0xFF) + - prefixZeros((int) objectGUID[9] & 0xFF) + - "-" + - prefixZeros((int) objectGUID[10] & 0xFF) + - prefixZeros((int) objectGUID[11] & 0xFF) + - prefixZeros((int) objectGUID[12] & 0xFF) + - prefixZeros((int) objectGUID[13] & 0xFF) + - prefixZeros((int) objectGUID[14] & 0xFF) + - prefixZeros((int) objectGUID[15] & 0xFF); - } - - private static String prefixZeros(int value) { - if (value <= 0xF) { - return "0" + Integer.toHexString(value); - } else { - return Integer.toHexString(value); - } - } - - public static boolean shouldUseTruststoreSpi(LdapMapConfig ldapConfig) { - boolean useSSL = ldapConfig.getConnectionUrl().toLowerCase().contains("ldaps://"); - boolean defaultUseTruststore = useSSL || ldapConfig.isStartTls(); - - String useTruststoreSpi = ldapConfig.getUseTruststoreSpi(); - if (useTruststoreSpi == null) { - return defaultUseTruststore; - } - - if (LDAPConstants.USE_TRUSTSTORE_NEVER.equals(useTruststoreSpi)) { - return false; - } - - return defaultUseTruststore; - } -} diff --git a/model/map-ldap/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory b/model/map-ldap/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory deleted file mode 100644 index c08e6a19609..00000000000 --- a/model/map-ldap/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.storage.ldap.LdapMapStorageProviderFactory diff --git a/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/Config.java b/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/Config.java deleted file mode 100644 index a272fbd3536..00000000000 --- a/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/Config.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap; - -import java.util.HashMap; -import java.util.Map; - -/** - * Simple configuration holder that allows for unit testing. - */ -public class Config extends org.keycloak.Config.SystemPropertiesScope { - - private final Map props; - - private Config(String prefix, Map props) { - super(prefix); - this.props = props; - } - - public Config() { - this("", new HashMap<>()); - } - - public void put(String key, String value) { - props.put(key, value); - } - - @Override - public String get(String key, String defaultValue) { - String val = props.get(prefix + key); - if (val != null) { - return val; - } - return super.get(key, defaultValue); - } - - @Override - public org.keycloak.Config.Scope scope(String... scope) { - StringBuilder sb = new StringBuilder(); - sb.append(prefix).append("."); - for (String s : scope) { - sb.append(s); - sb.append("."); - } - return new Config(sb.toString(), props); - } -} diff --git a/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/role/config/LdapMapRoleMapperConfigTest.java b/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/role/config/LdapMapRoleMapperConfigTest.java deleted file mode 100644 index edb70409559..00000000000 --- a/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/role/config/LdapMapRoleMapperConfigTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.role.config; - -import org.junit.Assert; -import org.junit.Test; -import org.keycloak.models.map.storage.ldap.Config; - -public class LdapMapRoleMapperConfigTest { - - @Test - public void shouldEscapeClientNameForPlaceholder() { - Config config = new Config(); - config.put(LdapMapRoleMapperConfig.CLIENT_ROLES_DN, "ou={0},dc=keycloak,dc=org"); - LdapMapRoleMapperConfig sut = new LdapMapRoleMapperConfig(config); - - Assert.assertEquals("ou=myclient,dc=keycloak,dc=org", - sut.getRolesDn("myclient")); - Assert.assertEquals("ou=\\ me\\=co\\\\ol\\, val\\=V\u00E9ronique,dc=keycloak,dc=org", - sut.getRolesDn(" me=co\\ol, val=V\u00E9ronique")); - } - -} \ No newline at end of file diff --git a/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/store/LdapMapEscapeStrategyTest.java b/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/store/LdapMapEscapeStrategyTest.java deleted file mode 100644 index dd1fc4179ea..00000000000 --- a/model/map-ldap/src/test/java/org/keycloak/models/map/storage/ldap/store/LdapMapEscapeStrategyTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022. 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.storage.ldap.store; - -import org.junit.Assert; -import org.junit.Test; - -/** - * Test escaping of characters. - * - * Test cases extracted from https://docs.oracle.com/cd/E29127_01/doc.111170/e28969/ds-ldif-search-filters.htm#gdxoy - */ -public class LdapMapEscapeStrategyTest { - - @Test - public void shouldEscapeUtf8CharactersForDefaultStrategy() { - Assert.assertEquals("V\\c3\\a9ronique", LdapMapEscapeStrategy.DEFAULT.escape("V\u00E9ronique")); - } - - @Test - public void shouldEscapeLdapQueryCharactersCharactersForDefaultStrategy() { - Assert.assertEquals("\\28\\29\\2a\\5c", LdapMapEscapeStrategy.DEFAULT.escape("()*\\")); - } - -} \ No newline at end of file diff --git a/model/map/pom.xml b/model/map/pom.xml deleted file mode 100644 index def50007fe4..00000000000 --- a/model/map/pom.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - keycloak-model-pom - org.keycloak - 999.0.0-SNAPSHOT - - 4.0.0 - - keycloak-model-map - Keycloak Model Map - - - - 11 - 11 - 11 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1-jboss-2 - - - - org.keycloak - keycloak-model-build-processor - ${project.version} - - - - org.keycloak.models.map.processor.GenerateEntityImplementationsProcessor - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - - - - org.keycloak - keycloak-core - - - org.keycloak - keycloak-server-spi - - - org.keycloak - keycloak-server-spi-private - - - - - org.jboss.resteasy - resteasy-core - provided - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - - - org.jboss.logging - jboss-logging - - - junit - junit - test - - - org.hamcrest - hamcrest - 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/authSession/AbstractRootAuthenticationSessionModel.java b/model/map/src/main/java/org/keycloak/models/map/authSession/AbstractRootAuthenticationSessionModel.java deleted file mode 100644 index a9f5df96fcc..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/AbstractRootAuthenticationSessionModel.java +++ /dev/null @@ -1,57 +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.authSession; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.sessions.RootAuthenticationSessionModel; - -import java.util.Objects; - -/** - * @author Martin Kanis - */ -public abstract class AbstractRootAuthenticationSessionModel implements RootAuthenticationSessionModel { - - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractRootAuthenticationSessionModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RootAuthenticationSessionModel)) return false; - - RootAuthenticationSessionModel that = (RootAuthenticationSessionModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionAdapter.java deleted file mode 100644 index 09ddbce51bb..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionAdapter.java +++ /dev/null @@ -1,232 +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.authSession; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.sessions.AuthenticationSessionModel; -import org.keycloak.sessions.RootAuthenticationSessionModel; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -/** - * @author Martin Kanis - */ -public class MapAuthenticationSessionAdapter implements AuthenticationSessionModel { - - private final KeycloakSession session; - private final MapRootAuthenticationSessionAdapter parent; - private final String tabId; - private final MapAuthenticationSessionEntity entity; - - public MapAuthenticationSessionAdapter(KeycloakSession session, MapRootAuthenticationSessionAdapter parent, - String tabId, MapAuthenticationSessionEntity entity) { - this.session = session; - this.parent = parent; - this.tabId = tabId; - this.entity = entity; - } - - @Override - public String getTabId() { - return tabId; - } - - @Override - public RootAuthenticationSessionModel getParentSession() { - return parent; - } - - @Override - public Map getExecutionStatus() { - Map executionStatus = entity.getExecutionStatuses(); - return executionStatus == null ? Collections.emptyMap() : Collections.unmodifiableMap(executionStatus); - } - - @Override - public void setExecutionStatus(String authenticator, ExecutionStatus status) { - Objects.requireNonNull(authenticator, "The provided authenticator can't be null!"); - Objects.requireNonNull(status, "The provided execution status can't be null!"); - this.entity.setExecutionStatus(authenticator, status); - } - - @Override - public void clearExecutionStatus() { - entity.setExecutionStatuses(null); - } - - @Override - public UserModel getAuthenticatedUser() { - return entity.getAuthUserId() == null ? null : session.users().getUserById(getRealm(), entity.getAuthUserId()); - } - - @Override - public void setAuthenticatedUser(UserModel user) { - String userId = (user == null) ? null : user.getId(); - entity.setAuthUserId(userId); - } - - @Override - public Set getRequiredActions() { - Set requiredActions = entity.getRequiredActions(); - return requiredActions == null ? Collections.emptySet() : Collections.unmodifiableSet(requiredActions); - } - - @Override - public void addRequiredAction(String action) { - Objects.requireNonNull(action, "The provided action can't be null!"); - entity.addRequiredAction(action); - } - - @Override - public void removeRequiredAction(String action) { - Objects.requireNonNull(action, "The provided action can't be null!"); - entity.removeRequiredAction(action); - } - - @Override - public void addRequiredAction(UserModel.RequiredAction action) { - Objects.requireNonNull(action, "The provided action can't be null!"); - addRequiredAction(action.name()); - } - - @Override - public void removeRequiredAction(UserModel.RequiredAction action) { - Objects.requireNonNull(action, "The provided action can't be null!"); - removeRequiredAction(action.name()); - } - - @Override - public void setUserSessionNote(String name, String value) { - entity.setUserSessionNote(name, value); - } - - @Override - public Map getUserSessionNotes() { - Map userSessionNotes = entity.getUserSessionNotes(); - return userSessionNotes == null ? Collections.emptyMap() : Collections.unmodifiableMap(userSessionNotes); - } - - @Override - public void clearUserSessionNotes() { - entity.setUserSessionNotes(null); - } - - @Override - public String getAuthNote(String name) { - Map authNotes = entity.getAuthNotes(); - return (name != null && authNotes != null) ? authNotes.get(name) : null; - } - - @Override - public void setAuthNote(String name, String value) { - entity.setAuthNote(name, value); - } - - @Override - public void removeAuthNote(String name) { - entity.removeAuthNote(name); - } - - @Override - public void clearAuthNotes() { - entity.setAuthNotes(null); - } - - @Override - public String getClientNote(String name) { - return (name != null) ? getClientNotes().get(name) : null; - } - - @Override - public void setClientNote(String name, String value) { - entity.setClientNote(name, value); - } - - @Override - public void removeClientNote(String name) { - entity.removeClientNote(name); - } - - @Override - public Map getClientNotes() { - Map clientNotes = entity.getClientNotes(); - return clientNotes == null ? Collections.emptyMap() : Collections.unmodifiableMap(clientNotes); - } - - @Override - public void clearClientNotes() { - entity.setClientNotes(null); - } - - @Override - public Set getClientScopes() { - Set clientScopes = entity.getClientScopes(); - return clientScopes == null ? Collections.emptySet() : Collections.unmodifiableSet(clientScopes); - } - - @Override - public void setClientScopes(Set clientScopes) { - Objects.requireNonNull(clientScopes, "The provided client scopes set can't be null!"); - entity.setClientScopes(clientScopes); - } - - @Override - public String getRedirectUri() { - return entity.getRedirectUri(); - } - - @Override - public void setRedirectUri(String uri) { - entity.setRedirectUri(uri); - } - - @Override - public RealmModel getRealm() { - return parent.getRealm(); - } - - @Override - public ClientModel getClient() { - return parent.getRealm().getClientById(entity.getClientUUID()); - } - - @Override - public String getAction() { - return entity.getAction(); - } - - @Override - public void setAction(String action) { - entity.setAction(action); - } - - @Override - public String getProtocol() { - return entity.getProtocol(); - } - - @Override - public void setProtocol(String method) { - entity.setProtocol(method); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionAuthNoteUpdateEvent.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionAuthNoteUpdateEvent.java deleted file mode 100644 index 3307b28f621..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionAuthNoteUpdateEvent.java +++ /dev/null @@ -1,86 +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.authSession; - -import org.keycloak.cluster.ClusterEvent; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -/** - * @author Martin Kanis - */ -public class MapAuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent { - - private String authSessionId; - private String tabId; - private String clientUUID; - - private Map authNotesFragment; - - /** - * Creates an instance of the event. - * @param authSessionId - * @param authNotesFragment - * @return Event. Note that {@code authNotesFragment} property is not thread safe which is fine for now. - */ - public static MapAuthenticationSessionAuthNoteUpdateEvent create(String authSessionId, String tabId, String clientUUID, - Map authNotesFragment) { - MapAuthenticationSessionAuthNoteUpdateEvent event = new MapAuthenticationSessionAuthNoteUpdateEvent(); - event.authSessionId = authSessionId; - event.tabId = tabId; - event.clientUUID = clientUUID; - event.authNotesFragment = new LinkedHashMap<>(authNotesFragment); - return event; - } - - public String getAuthSessionId() { - return authSessionId; - } - - public String getTabId() { - return tabId; - } - - public String getClientUUID() { - return clientUUID; - } - - public Map getAuthNotesFragment() { - return authNotesFragment; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MapAuthenticationSessionAuthNoteUpdateEvent that = (MapAuthenticationSessionAuthNoteUpdateEvent) o; - return Objects.equals(authSessionId, that.authSessionId) && Objects.equals(tabId, that.tabId) && Objects.equals(clientUUID, that.clientUUID); - } - - @Override - public int hashCode() { - return Objects.hash(authSessionId, tabId, clientUUID); - } - - @Override - public String toString() { - return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, tabId=%s, clientUUID=%s, authNotesFragment=%s ]", - authSessionId, tabId, clientUUID, authNotesFragment); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java deleted file mode 100644 index 39b2b301f2e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java +++ /dev/null @@ -1,92 +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.authSession; - -import org.keycloak.models.map.annotations.CollectionKey; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.sessions.AuthenticationSessionModel; - -import java.util.Map; -import java.util.Set; - -/** - * @author Martin Kanis - */ -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapAuthenticationSessionEntity extends UpdatableEntity { - - @CollectionKey - String getTabId(); - void setTabId(String tabId); - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the authentication session entity was created. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the authentication session entity was created. - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - Map getUserSessionNotes(); - void setUserSessionNotes(Map userSessionNotes); - void setUserSessionNote(String name, String value); - - String getClientUUID(); - void setClientUUID(String clientUUID); - - String getAuthUserId(); - void setAuthUserId(String authUserId); - - String getRedirectUri(); - void setRedirectUri(String redirectUri); - - String getAction(); - void setAction(String action); - - Set getClientScopes(); - void setClientScopes(Set clientScopes); - - Set getRequiredActions(); - void setRequiredActions(Set requiredActions); - void addRequiredAction(String requiredAction); - void removeRequiredAction(String action); - - String getProtocol(); - void setProtocol(String protocol); - - Map getClientNotes(); - void setClientNotes(Map clientNotes); - void setClientNote(String name, String value); - void removeClientNote(String name); - - Map getAuthNotes(); - void setAuthNotes(Map authNotes); - void setAuthNote(String name, String value); - void removeAuthNote(String name); - - Map getExecutionStatuses(); - void setExecutionStatuses(Map executionStatus); - void setExecutionStatus(String authenticator, AuthenticationSessionModel.ExecutionStatus status); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java deleted file mode 100644 index e0ef2bd6bb9..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java +++ /dev/null @@ -1,164 +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.authSession; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Base64Url; -import org.keycloak.common.util.SecretGenerator; -import org.keycloak.common.util.Time; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.utils.SessionExpiration; -import org.keycloak.sessions.AuthenticationSessionModel; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.keycloak.models.utils.SessionExpiration.getAuthSessionLifespan; - -/** - * @author Martin Kanis - */ -public class MapRootAuthenticationSessionAdapter extends AbstractRootAuthenticationSessionModel { - - private static final Logger LOG = Logger.getLogger(MapRootAuthenticationSessionAdapter.class); - - private int authSessionsLimit; - - private static Comparator TIMESTAMP_COMPARATOR = Comparator.comparingLong(MapAuthenticationSessionEntity::getTimestamp); - - public MapRootAuthenticationSessionAdapter(KeycloakSession session, RealmModel realm, MapRootAuthenticationSessionEntity entity, int authSessionsLimit) { - super(session, realm, entity); - this.authSessionsLimit = authSessionsLimit; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public RealmModel getRealm() { - return session.realms().getRealm(entity.getRealmId()); - } - - @Override - public int getTimestamp() { - return TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(TimeAdapter.fromMilliSecondsToSeconds(entity.getTimestamp())); - } - - @Override - public void setTimestamp(int timestamp) { - entity.setTimestamp(TimeAdapter.fromSecondsToMilliseconds(timestamp)); - entity.setExpiration(TimeAdapter.fromSecondsToMilliseconds(SessionExpiration.getAuthSessionExpiration(realm, timestamp))); - } - - @Override - public Map getAuthenticationSessions() { - return Optional.ofNullable(entity.getAuthenticationSessions()).orElseGet(Collections::emptySet).stream() - .collect(Collectors.toMap(MapAuthenticationSessionEntity::getTabId, this::toAdapter)); - } - - @Override - public AuthenticationSessionModel getAuthenticationSession(ClientModel client, String tabId) { - if (client == null || tabId == null) { - return null; - } - - return entity.getAuthenticationSession(tabId).map(this::toAdapter).map(this::setAuthContext).orElse(null); - } - - @Override - public AuthenticationSessionModel createAuthenticationSession(ClientModel client) { - Objects.requireNonNull(client, "The provided client can't be null!"); - - Set authenticationSessions = entity.getAuthenticationSessions(); - if (authenticationSessions != null && authenticationSessions.size() >= authSessionsLimit) { - String tabId = authenticationSessions.stream().min(TIMESTAMP_COMPARATOR).map(MapAuthenticationSessionEntity::getTabId).orElse(null); - - if (tabId != null) { - LOG.debugf("Reached limit (%s) of active authentication sessions per a root authentication session. Removing oldest authentication session with TabId %s.", authSessionsLimit, tabId); - - // remove the oldest authentication session - entity.removeAuthenticationSession(tabId); - } - } - - MapAuthenticationSessionEntity authSessionEntity = DeepCloner.DUMB_CLONER.newInstance(MapAuthenticationSessionEntity.class); - authSessionEntity.setClientUUID(client.getId()); - - long timestamp = Time.currentTimeMillis(); - authSessionEntity.setTimestamp(timestamp); - String tabId = generateTabId(); - authSessionEntity.setTabId(tabId); - - entity.addAuthenticationSession(authSessionEntity); - - // Update our timestamp when adding new authenticationSession - entity.setTimestamp(timestamp); - - int authSessionLifespanSeconds = getAuthSessionLifespan(realm); - entity.setExpiration(timestamp + TimeAdapter.fromSecondsToMilliseconds(authSessionLifespanSeconds)); - - return entity.getAuthenticationSession(tabId).map(this::toAdapter).map(this::setAuthContext).orElse(null); - } - - @Override - public void removeAuthenticationSessionByTabId(String tabId) { - Boolean result = entity.removeAuthenticationSession(tabId); - if (result == null || result) { - if (entity.getAuthenticationSessions().isEmpty()) { - session.authenticationSessions().removeRootAuthenticationSession(realm, this); - } else { - long timestamp = Time.currentTimeMillis(); - entity.setTimestamp(timestamp); - int authSessionLifespanSeconds = getAuthSessionLifespan(realm); - entity.setExpiration(timestamp + TimeAdapter.fromSecondsToMilliseconds(authSessionLifespanSeconds)); - } - } - } - - @Override - public void restartSession(RealmModel realm) { - entity.setAuthenticationSessions(null); - long timestamp = Time.currentTimeMillis(); - entity.setTimestamp(timestamp); - int authSessionLifespanSeconds = getAuthSessionLifespan(realm); - entity.setExpiration(timestamp + TimeAdapter.fromSecondsToMilliseconds(authSessionLifespanSeconds)); - } - - private String generateTabId() { - return Base64Url.encode(SecretGenerator.getInstance().randomBytes(8)); - } - - private MapAuthenticationSessionAdapter toAdapter(MapAuthenticationSessionEntity entity) { - return new MapAuthenticationSessionAdapter(session, this, entity.getTabId(), entity); - } - - private MapAuthenticationSessionAdapter setAuthContext(MapAuthenticationSessionAdapter adapter) { - session.getContext().setAuthenticationSession(adapter); - return adapter; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionEntity.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionEntity.java deleted file mode 100644 index 37c8b724595..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionEntity.java +++ /dev/null @@ -1,92 +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.authSession; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Collections; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -/** - * @author Martin Kanis - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity.AbstractRootAuthenticationSessionEntity" -) -@DeepCloner.Root -public interface MapRootAuthenticationSessionEntity extends AbstractEntity, UpdatableEntity, ExpirableEntity { - - public abstract class AbstractRootAuthenticationSessionEntity extends UpdatableEntity.Impl implements MapRootAuthenticationSessionEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - @Override - public boolean isUpdated() { - return this.updated || - Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationSessionEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - } - } - - String getRealmId(); - void setRealmId(String realmId); - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the root authentication session entity was created or - * updated during an authentication process. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the root authentication session entity was created or - * updated during an authentication process. - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - Set getAuthenticationSessions(); - void setAuthenticationSessions(Set authenticationSessions); - Optional getAuthenticationSession(String tabId); - void addAuthenticationSession(MapAuthenticationSessionEntity authenticationSession); - Boolean removeAuthenticationSession(String tabId); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java deleted file mode 100644 index 12e1437bfc9..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java +++ /dev/null @@ -1,183 +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.authSession; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.sessions.AuthenticationSessionCompoundId; -import org.keycloak.sessions.AuthenticationSessionProvider; -import org.keycloak.sessions.RootAuthenticationSessionModel; - -import org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields; - -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; -import static org.keycloak.models.utils.SessionExpiration.getAuthSessionLifespan; - -/** - * @author Martin Kanis - */ -public class MapRootAuthenticationSessionProvider implements AuthenticationSessionProvider { - - private static final Logger LOG = Logger.getLogger(MapRootAuthenticationSessionProvider.class); - private final KeycloakSession session; - protected final MapStorage store; - private int authSessionsLimit; - private final boolean storeHasRealmId; - - public MapRootAuthenticationSessionProvider(KeycloakSession session, - MapStorage sessionStore, - int authSessionsLimit) { - this.session = session; - this.store = sessionStore; - this.authSessionsLimit = authSessionsLimit; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm) { - return origEntity -> { - if (isExpired(origEntity, true)) { - storeWithRealm(realm).delete(origEntity.getId()); - return null; - } else { - return new MapRootAuthenticationSessionAdapter(session, realm, origEntity, authSessionsLimit); - } - }; - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private Predicate entityRealmFilter(String realmId) { - if (realmId == null) { - return c -> false; - } - return entity -> Objects.equals(realmId, entity.getRealmId()); - } - - @Override - public RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm) { - Objects.requireNonNull(realm, "The provided realm can't be null!"); - return createRootAuthenticationSession(realm, null); - } - - @Override - public RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm, String id) { - Objects.requireNonNull(realm, "The provided realm can't be null!"); - - LOG.tracef("createRootAuthenticationSession(%s)%s", realm.getName(), getShortStackTrace()); - - // create map authentication session entity - MapRootAuthenticationSessionEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapRootAuthenticationSessionEntity.class); - entity.setId(id); - entity.setRealmId(realm.getId()); - long timestamp = Time.currentTimeMillis(); - entity.setTimestamp(timestamp); - - int authSessionLifespanSeconds = getAuthSessionLifespan(realm); - entity.setExpiration(timestamp + TimeAdapter.fromSecondsToMilliseconds(authSessionLifespanSeconds)); - - if (id != null && storeWithRealm(realm).exists(id)) { - throw new ModelDuplicateException("Root authentication session exists: " + entity.getId()); - } - - entity = storeWithRealm(realm).create(entity); - - return entityToAdapterFunc(realm).apply(entity); - } - - @Override - public RootAuthenticationSessionModel getRootAuthenticationSession(RealmModel realm, String authenticationSessionId) { - Objects.requireNonNull(realm, "The provided realm can't be null!"); - if (authenticationSessionId == null) { - return null; - } - - LOG.tracef("getRootAuthenticationSession(%s, %s)%s", realm.getName(), authenticationSessionId, getShortStackTrace()); - - MapRootAuthenticationSessionEntity entity = storeWithRealm(realm).read(authenticationSessionId); - return (entity == null || !entityRealmFilter(realm.getId()).test(entity)) - ? null - : entityToAdapterFunc(realm).apply(entity); - } - - @Override - public void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession) { - Objects.requireNonNull(authenticationSession, "The provided root authentication session can't be null!"); - storeWithRealm(realm).delete(authenticationSession.getId()); - } - - @Override - public void removeAllExpired() { - LOG.tracef("removeAllExpired()%s", getShortStackTrace()); - LOG.warnf("Clearing expired entities should not be triggered manually. It is responsibility of the store to clear these."); - } - - @Override - public void removeExpired(RealmModel realm) { - LOG.tracef("removeExpired(%s)%s", realm, getShortStackTrace()); - LOG.warnf("Clearing expired entities should not be triggered manually. It is responsibility of the store to clear these."); - } - - @Override - public void onRealmRemoved(RealmModel realm) { - Objects.requireNonNull(realm, "The provided realm can't be null!"); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void onClientRemoved(RealmModel realm, ClientModel client) { - - } - - @Override - public void updateNonlocalSessionAuthNotes(AuthenticationSessionCompoundId compoundId, Map authNotesFragment) { - if (compoundId == null) { - return; - } - Objects.requireNonNull(authNotesFragment, "The provided authentication's notes map can't be null!"); - } - - @Override - public void close() { - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java deleted file mode 100644 index e03aa48c85b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java +++ /dev/null @@ -1,78 +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.authSession; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.provider.ProviderConfigurationBuilder; -import org.keycloak.sessions.AuthenticationSessionProviderFactory; - -import org.keycloak.sessions.RootAuthenticationSessionModel; - -import java.util.List; - -/** - * @author Martin Kanis - */ -public class MapRootAuthenticationSessionProviderFactory extends AbstractMapProviderFactory - implements AuthenticationSessionProviderFactory { - - public static final String AUTH_SESSIONS_LIMIT = "authSessionsLimit"; - - public static final int DEFAULT_AUTH_SESSIONS_LIMIT = 300; - - private int authSessionsLimit; - - public MapRootAuthenticationSessionProviderFactory() { - super(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionProvider.class); - } - - @Override - public void init(Config.Scope config) { - super.init(config); - - // get auth sessions limit from config or use default if not provided - int configInt = config.getInt(AUTH_SESSIONS_LIMIT, DEFAULT_AUTH_SESSIONS_LIMIT); - // use default if provided value is not a positive number - authSessionsLimit = (configInt <= 0) ? DEFAULT_AUTH_SESSIONS_LIMIT : configInt; - } - - @Override - public List getConfigMetadata() { - return ProviderConfigurationBuilder.create() - .property() - .name("authSessionsLimit") - .type("int") - .helpText("The maximum number of concurrent authentication sessions per RootAuthenticationSession.") - .defaultValue(DEFAULT_AUTH_SESSIONS_LIMIT) - .add() - .build(); - } - - @Override - public MapRootAuthenticationSessionProvider createNew(KeycloakSession session) { - return new MapRootAuthenticationSessionProvider(session, getMapStorage(session), authSessionsLimit); - } - - @Override - public String getHelpText() { - return "Authentication session provider"; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStore.java deleted file mode 100644 index 542d0002099..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStore.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.authorization; - -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.storage.MapStorage; - - -/** - * @author mhajas - */ -public class MapAuthorizationStore implements StoreFactory { - - private final MapPolicyStore policyStore; - private final MapResourceServerStore resourceServerStore; - private final MapResourceStore resourceStore; - private final MapScopeStore scopeStore; - private final MapPermissionTicketStore permissionTicketStore; - private boolean readOnly; - - public MapAuthorizationStore(MapStorage permissionTicketStore, - MapStorage policyStore, - MapStorage resourceServerStore, - MapStorage resourceStore, - MapStorage scopeStore, - AuthorizationProvider provider) { - this.permissionTicketStore = new MapPermissionTicketStore(permissionTicketStore, provider); - this.policyStore = new MapPolicyStore(policyStore, provider); - this.resourceServerStore = new MapResourceServerStore(resourceServerStore, provider); - this.resourceStore = new MapResourceStore(resourceStore, provider); - this.scopeStore = new MapScopeStore(scopeStore, provider); - } - - @Override - public MapResourceStore getResourceStore() { - return resourceStore; - } - - @Override - public MapResourceServerStore getResourceServerStore() { - return resourceServerStore; - } - - @Override - public MapScopeStore getScopeStore() { - return scopeStore; - } - - @Override - public MapPolicyStore getPolicyStore() { - return policyStore; - } - - @Override - public MapPermissionTicketStore getPermissionTicketStore() { - return permissionTicketStore; - } - - @Override - public void setReadOnly(boolean readOnly) { - this.readOnly = readOnly; - } - - @Override - public boolean isReadOnly() { - return readOnly; - } - - @Override - public void close() { - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java deleted file mode 100644 index 3b587e91590..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.authorization; - -import org.keycloak.Config; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.AuthorizationStoreFactory; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.common.Profile; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.RESOURCE_SERVER_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.uniqueCounter; - -/** - * @author mhajas - */ -public class MapAuthorizationStoreFactory implements AmphibianProviderFactory, AuthorizationStoreFactory, EnvironmentDependentProviderFactory, InvalidationHandler { - - public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID; - - private Config.Scope storageConfigScope; - private final String uniqueKey = MapAuthorizationStoreFactory.class.getName() + uniqueCounter.incrementAndGet(); - - @Override - public StoreFactory create(KeycloakSession session) { - MapAuthorizationStore authzStore = session.getAttribute(uniqueKey, MapAuthorizationStore.class); - - if (authzStore != null) return authzStore; - - final MapStorageProvider mapStorageProvider = AbstractMapProviderFactory.getProviderFactoryOrComponentFactory(session, storageConfigScope).create(session); - AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class); - - MapStorage permissionTicketStore = mapStorageProvider.getMapStorage(PermissionTicket.class); - MapStorage policyStore = mapStorageProvider.getMapStorage(Policy.class); - MapStorage resourceServerStore = mapStorageProvider.getMapStorage(ResourceServer.class); - MapStorage resourceStore = mapStorageProvider.getMapStorage(Resource.class); - MapStorage scopeStore = mapStorageProvider.getMapStorage(Scope.class); - - authzStore = new MapAuthorizationStore( - permissionTicketStore, - policyStore, - resourceServerStore, - resourceStore, - scopeStore, - provider - ); - - session.setAttribute(uniqueKey, authzStore); - return authzStore; - } - - @Override - public void init(org.keycloak.Config.Scope config) { - this.storageConfigScope = config.scope("storage"); - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public String getHelpText() { - return "Authorization store provider"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - MapAuthorizationStore authorizationStore = (MapAuthorizationStore) session.getProvider(StoreFactory.class); - RealmModel realm = (RealmModel) params[0]; - - authorizationStore.getScopeStore().preRemove(realm); - authorizationStore.getPolicyStore().preRemove(realm); - authorizationStore.getResourceStore().preRemove(realm); - authorizationStore.getPermissionTicketStore().preRemove(realm); - authorizationStore.getResourceServerStore().preRemove(realm); - } else if (type == RESOURCE_SERVER_BEFORE_REMOVE) { - MapAuthorizationStore authorizationStore = (MapAuthorizationStore) session.getProvider(StoreFactory.class); - RealmModel realm = (RealmModel) params[0]; - ResourceServer resourceServer = (ResourceServer) params[1]; - - - authorizationStore.getScopeStore().preRemove(realm, resourceServer); - authorizationStore.getPolicyStore().preRemove(realm, resourceServer); - authorizationStore.getResourceStore().preRemove(realm, resourceServer); - authorizationStore.getPermissionTicketStore().preRemove(realm, resourceServer); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java deleted file mode 100644 index 98c31d88162..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright 2022 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.authorization; - -import org.jboss.logging.Logger; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.UserManagedPermissionUtil; -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.PermissionTicket.SearchableFields; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.PermissionTicketStore; -import org.keycloak.authorization.store.ResourceServerStore; -import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.common.util.Time; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.adapter.MapPermissionTicketAdapter; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; -import static org.keycloak.utils.StreamsUtil.distinctByKey; -import static org.keycloak.utils.StreamsUtil.paginatedStream; - -public class MapPermissionTicketStore implements PermissionTicketStore { - - private static final Logger LOG = Logger.getLogger(MapPermissionTicketStore.class); - private final AuthorizationProvider authorizationProvider; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapPermissionTicketStore(MapStorage permissionTicketStore, AuthorizationProvider provider) { - this.authorizationProvider = provider; - this.store = permissionTicketStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm, ResourceServer resourceServer) { - return origEntity -> new MapPermissionTicketAdapter(realm, resourceServer, origEntity, authorizationProvider.getStoreFactory()); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private DefaultModelCriteria forRealmAndResourceServer(RealmModel realm, ResourceServer resourceServer) { - final DefaultModelCriteria mcb = DefaultModelCriteria.criteria() - .compare(PermissionTicket.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return resourceServer == null - ? mcb - : mcb.compare(SearchableFields.RESOURCE_SERVER_ID, Operator.EQ, - resourceServer.getId()); - } - - @Override - public long count(ResourceServer resourceServer, Map attributes) { - DefaultModelCriteria mcb = forRealmAndResourceServer(resourceServer.getRealm(), resourceServer).and( - attributes.entrySet().stream() - .map(this::filterEntryToDefaultModelCriteria) - .toArray(DefaultModelCriteria[]::new) - ); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).getCount(withCriteria(mcb)); - } - - @Override - public PermissionTicket create(ResourceServer resourceServer, Resource resource, Scope scope, String requester) { - LOG.tracef("create(%s, %s, %s, %s)%s", resource, scope, requester, resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - String owner = authorizationProvider.getStoreFactory().getResourceStore().findById(realm, resourceServer, resource.getId()).getOwner(); - - - // @UniqueConstraint(columnNames = {"OWNER", "REQUESTER", "RESOURCE_SERVER_ID", "RESOURCE_ID", "SCOPE_ID"}) - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.OWNER, Operator.EQ, owner) - .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resource.getId()) - .compare(SearchableFields.REQUESTER, Operator.EQ, requester); - - if (scope != null) { - mcb = mcb.compare(SearchableFields.SCOPE_ID, Operator.EQ, scope.getId()); - } - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("Permission ticket for resource server: '" + resourceServer.getId() - + ", Resource: " + resource + ", owner: " + owner + ", scopeId: " + scope + " already exists."); - } - - MapPermissionTicketEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapPermissionTicketEntity.class); - entity.setResourceId(resource.getId()); - entity.setRequester(requester); - entity.setCreatedTimestamp(Time.currentTimeMillis()); - - if (scope != null) { - entity.setScopeId(scope.getId()); - } - - entity.setOwner(owner); - entity.setResourceServerId(resourceServer.getId()); - entity.setRealmId(realm.getId()); - - entity = storeWithRealm(realm).create(entity); - - return entity == null ? null : entityToAdapterFunc(realm, resourceServer).apply(entity); - } - - @Override - public void delete(RealmModel realm, String id) { - LOG.tracef("delete(%s)%s", id, getShortStackTrace()); - - PermissionTicket permissionTicket = findById(realm, null, id); - if (permissionTicket == null) return; - - storeWithRealm(realm).delete(id); - UserManagedPermissionUtil.removePolicy(permissionTicket, authorizationProvider.getStoreFactory()); - } - - @Override - public PermissionTicket findById(RealmModel realm, ResourceServer resourceServer, String id) { - LOG.tracef("findById(%s, %s)%s", id, resourceServer, getShortStackTrace()); - - if (id == null) return null; - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.ID, Operator.EQ, id))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public List findByResource(ResourceServer resourceServer, Resource resource) { - LOG.tracef("findByResource(%s, %s)%s", resource, resourceServer, getShortStackTrace()); - - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resource.getId()))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public List findByScope(ResourceServer resourceServer, Scope scope) { - LOG.tracef("findByScope(%s, %s)%s", scope, resourceServer, getShortStackTrace()); - - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.SCOPE_ID, Operator.EQ, scope.getId()))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public List find(RealmModel realm, ResourceServer resourceServer, Map attributes, Integer firstResult, Integer maxResult) { - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer); - - if (attributes.containsKey(PermissionTicket.FilterOption.RESOURCE_NAME)) { - String expectedResourceName = attributes.remove(PermissionTicket.FilterOption.RESOURCE_NAME); - - Map filterOptionStringMap = new EnumMap<>(Resource.FilterOption.class); - - filterOptionStringMap.put(Resource.FilterOption.EXACT_NAME, new String[]{expectedResourceName}); - - List r = authorizationProvider.getStoreFactory().getResourceStore().find(realm, resourceServer, filterOptionStringMap, null, null); - if (r == null || r.isEmpty()) { - return Collections.emptyList(); - } - mcb = mcb.compare(SearchableFields.RESOURCE_ID, Operator.IN, r.stream().map(Resource::getId)); - } - - mcb = mcb.and( - attributes.entrySet().stream() - .map(this::filterEntryToDefaultModelCriteria) - .toArray(DefaultModelCriteria[]::new) - ); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResult, SearchableFields.ID)) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - private DefaultModelCriteria filterEntryToDefaultModelCriteria(Map.Entry entry) { - PermissionTicket.FilterOption name = entry.getKey(); - String value = entry.getValue(); - - DefaultModelCriteria mcb = criteria(); - switch (name) { - case ID: - case SCOPE_ID: - case RESOURCE_ID: - case OWNER: - case REQUESTER: - case POLICY_ID: - return mcb.compare(name.getSearchableModelField(), Operator.EQ, value); - case SCOPE_IS_NULL: - case GRANTED: - case REQUESTER_IS_NULL: { - Operator op = Operator.NOT_EXISTS; - if (Boolean.parseBoolean(value)) { - op = Operator.EXISTS; - } - return mcb.compare(name.getSearchableModelField(), op); - } - case POLICY_IS_NOT_NULL: - return mcb.compare(SearchableFields.REQUESTER, Operator.NOT_EXISTS); - default: - throw new IllegalArgumentException("Unsupported filter [" + name + "]"); - - } - } - - @Override - public List findGranted(ResourceServer resourceServer, String userId) { - Map filters = new EnumMap<>(PermissionTicket.FilterOption.class); - - filters.put(PermissionTicket.FilterOption.GRANTED, Boolean.TRUE.toString()); - filters.put(PermissionTicket.FilterOption.REQUESTER, userId); - - return find(resourceServer.getRealm(), resourceServer, filters, null, null); - } - - @Override - public List findGranted(ResourceServer resourceServer, String resourceName, String userId) { - Map filters = new EnumMap<>(PermissionTicket.FilterOption.class); - - filters.put(PermissionTicket.FilterOption.RESOURCE_NAME, resourceName); - filters.put(PermissionTicket.FilterOption.GRANTED, Boolean.TRUE.toString()); - filters.put(PermissionTicket.FilterOption.REQUESTER, userId); - - return find(resourceServer.getRealm(), resourceServer, filters, null, null); - } - - @Override - public List findGrantedResources(RealmModel realm, String requester, String name, Integer first, Integer max) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REQUESTER, Operator.EQ, requester) - .compare(SearchableFields.GRANTED_TIMESTAMP, Operator.EXISTS) - .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - Function ticketResourceMapper; - - ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); - ResourceServerStore resourceServerStore = authorizationProvider.getStoreFactory().getResourceServerStore(); - if (name != null) { - ticketResourceMapper = ticket -> { - Map filterOptionMap = new EnumMap<>(Resource.FilterOption.class); - - filterOptionMap.put(Resource.FilterOption.ID, new String[] {ticket.getResourceId()}); - filterOptionMap.put(Resource.FilterOption.NAME, new String[] {name}); - - List resource = resourceStore.find(realm, resourceServerStore.findById(realm, ticket.getResourceServerId()), filterOptionMap, -1, 1); - - return resource.isEmpty() ? null : resource.get(0); - }; - } else { - ticketResourceMapper = ticket -> resourceStore - .findById(realm, resourceServerStore.findById(realm, ticket.getResourceServerId()), ticket.getResourceId()); - } - - return paginatedStream(storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.RESOURCE_ID, ASCENDING)) - .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)) - .map(ticketResourceMapper) - .filter(Objects::nonNull), first, max) - .collect(Collectors.toList()); - } - - @Override - public List findGrantedOwnerResources(RealmModel realm, String owner, Integer firstResult, Integer maxResults) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.OWNER, Operator.EQ, owner) - .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); - ResourceServerStore resourceServerStore = authorizationProvider.getStoreFactory().getResourceServerStore(); - - return paginatedStream(storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.RESOURCE_ID, ASCENDING)) - .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)), firstResult, maxResults) - .map(ticket -> resourceStore.findById(realm, resourceServerStore.findById(realm, ticket.getResourceServerId()), ticket.getResourceId())) - .collect(Collectors.toList()); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - public void preRemove(RealmModel realm, ResourceServer resourceServer) { - LOG.tracef("preRemove(%s, %s)%s", realm, resourceServer, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(forRealmAndResourceServer(resourceServer.getRealm(), resourceServer))); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java deleted file mode 100644 index 907c2ec76e7..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2022 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.authorization; - -import org.jboss.logging.Logger; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Policy.SearchableFields; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.adapter.MapPolicyAdapter; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapPolicyStore implements PolicyStore { - - private static final Logger LOG = Logger.getLogger(MapPolicyStore.class); - private final AuthorizationProvider authorizationProvider; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapPolicyStore(MapStorage policyStore, AuthorizationProvider provider) { - this.authorizationProvider = provider; - this.store = policyStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm, ResourceServer resourceServer) { - return origEntity -> new MapPolicyAdapter(realm, resourceServer, origEntity, authorizationProvider.getStoreFactory()); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private DefaultModelCriteria forRealmAndResourceServer(RealmModel realm, ResourceServer resourceServer) { - DefaultModelCriteria mcb = DefaultModelCriteria.criteria() - .compare(Policy.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return resourceServer == null - ? mcb - : mcb.compare(SearchableFields.RESOURCE_SERVER_ID, Operator.EQ, - resourceServer.getId()); - } - - @Override - public Policy create(ResourceServer resourceServer, AbstractPolicyRepresentation representation) { - LOG.tracef("create(%s, %s, %s)%s", representation.getId(), resourceServer.getId(), resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - // @UniqueConstraint(columnNames = {"NAME", "RESOURCE_SERVER_ID"}) - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.NAME, Operator.EQ, representation.getName()); - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("Policy with name '" + representation.getName() + "' for " + resourceServer.getId() + " already exists"); - } - - String uid = representation.getId(); - MapPolicyEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapPolicyEntity.class); - entity.setId(uid); - entity.setType(representation.getType()); - entity.setName(representation.getName()); - entity.setResourceServerId(resourceServer.getId()); - entity.setRealmId(resourceServer.getRealm().getId()); - - entity = storeWithRealm(realm).create(entity); - - return entity == null ? null : entityToAdapterFunc(realm, resourceServer).apply(entity); - } - - @Override - public void delete(RealmModel realm, String id) { - LOG.tracef("delete(%s)%s", id, getShortStackTrace()); - - Policy policyEntity = findById(realm, null, id); - if (policyEntity == null) return; - - storeWithRealm(realm).delete(id); - } - - @Override - public Policy findById(RealmModel realm, ResourceServer resourceServer, String id) { - LOG.tracef("findById(%s, %s)%s", id, resourceServer, getShortStackTrace()); - - if (id == null) return null; - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.ID, Operator.EQ, id))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public Policy findByName(ResourceServer resourceServer, String name) { - LOG.tracef("findByName(%s, %s)%s", name, resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.NAME, Operator.EQ, name))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public List findByResourceServer(ResourceServer resourceServer) { - LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public List find(RealmModel realm, ResourceServer resourceServer, Map attributes, Integer firstResult, Integer maxResults) { - LOG.tracef("findByResourceServer(%s, %s, %d, %d)%s", attributes, resourceServer, firstResult, maxResults, getShortStackTrace()); - - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer).and( - attributes.entrySet().stream() - .map(this::filterEntryToDefaultModelCriteria) - .filter(Objects::nonNull) - .toArray(DefaultModelCriteria[]::new) - ); - - if (!attributes.containsKey(Policy.FilterOption.OWNER) && !attributes.containsKey(Policy.FilterOption.ANY_OWNER)) { - mcb = mcb.compare(SearchableFields.OWNER, Operator.NOT_EXISTS); - } - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - private DefaultModelCriteria filterEntryToDefaultModelCriteria(Map.Entry entry) { - Policy.FilterOption name = entry.getKey(); - String[] value = entry.getValue(); - - DefaultModelCriteria mcb = criteria(); - switch (name) { - case ID: - case SCOPE_ID: - case RESOURCE_ID: - case OWNER: - return mcb.compare(name.getSearchableModelField(), Operator.IN, Arrays.asList(value)); - case PERMISSION: { - mcb = mcb.compare(SearchableFields.TYPE, Operator.IN, Arrays.asList("resource", "scope", "uma")); - - if (!Boolean.parseBoolean(value[0])) { - mcb = DefaultModelCriteria.criteria().not(mcb); // TODO: create NOT_IN operator - } - - return mcb; - } - case ANY_OWNER: - return null; - case CONFIG: - if (value.length != 2) { - throw new IllegalArgumentException("Config filter option requires value with two items: [config_name, expected_config_value]"); - } - - value[1] = "%" + value[1] + "%"; - return mcb.compare(SearchableFields.CONFIG, Operator.LIKE, (Object[]) value); - case TYPE: - case NAME: - return mcb.compare(name.getSearchableModelField(), Operator.ILIKE, "%" + value[0] + "%"); - default: - throw new IllegalArgumentException("Unsupported filter [" + name + "]"); - - } - } - - @Override - public void findByResource(ResourceServer resourceServer, Resource resource, Consumer consumer) { - LOG.tracef("findByResource(%s, %s, %s)%s", resourceServer, resource, consumer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resource.getId()))) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(consumer); - } - - @Override - public void findByResourceType(ResourceServer resourceServer, String type, Consumer policyConsumer) { - LOG.tracef("findByResourceType(%s, %s)%s", resourceServer, type, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.CONFIG, Operator.LIKE, (Object[]) new String[]{"defaultResourceType", type}))) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(policyConsumer); - } - - @Override - public List findByScopes(ResourceServer resourceServer, List scopes) { - LOG.tracef("findByScopes(%s, %s)%s", resourceServer, scopes, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes.stream().map(Scope::getId)))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public void findByScopes(ResourceServer resourceServer, Resource resource, List scopes, Consumer consumer) { - LOG.tracef("findByResourceType(%s, %s, %s)%s", resourceServer, resource, scopes, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.TYPE, Operator.EQ, "scope") - .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes.stream().map(Scope::getId)); - - if (resource != null) { - mcb = mcb.compare(SearchableFields.RESOURCE_ID, Operator.EQ, resource.getId()); - // @NamedQuery(name="findPolicyIdByNullResourceScope", query="PolicyEntity pe left join fetch pe.config c inner join pe.scopes s where pe.resourceServer.id = :serverId and pe.type = 'scope' and pe.resources is empty and s.id in (:scopeIds) and not exists (select pec from pe.config pec where KEY(pec) = 'defaultResourceType')"), - } else { - mcb = mcb.compare(SearchableFields.RESOURCE_ID, Operator.NOT_EXISTS) - .compare(SearchableFields.CONFIG, Operator.NOT_EXISTS, (Object[]) new String[] {"defaultResourceType"}); - } - - storeWithRealm(realm).read(withCriteria(mcb)).map(entityToAdapterFunc(realm, resourceServer)).forEach(consumer); - } - - @Override - public List findByType(ResourceServer resourceServer, String type) { - LOG.tracef("findByType(%s, %s)%s", resourceServer, type, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.TYPE, Operator.EQ, type))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public List findDependentPolicies(ResourceServer resourceServer, String id) { - RealmModel realm = resourceServer.getRealm(); - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.ASSOCIATED_POLICY_ID, Operator.EQ, id))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - public void preRemove(RealmModel realm, ResourceServer resourceServer) { - LOG.tracef("preRemove(%s, %s)%s", realm, resourceServer, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(forRealmAndResourceServer(resourceServer.getRealm(), resourceServer))); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java deleted file mode 100644 index 2edb3bf6d6f..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.authorization; - -import org.jboss.logging.Logger; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.ResourceServer.SearchableFields; -import org.keycloak.authorization.store.ResourceServerStore; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.ModelException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.adapter.MapResourceServerAdapter; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.storage.StorageId; - -import java.util.Objects; -import java.util.function.Function; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.RESOURCE_SERVER_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.RESOURCE_SERVER_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapResourceServerStore implements ResourceServerStore { - - private static final Logger LOG = Logger.getLogger(MapResourceServerStore.class); - private final AuthorizationProvider authorizationProvider; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapResourceServerStore(MapStorage resourceServerStore, AuthorizationProvider provider) { - this.authorizationProvider = provider; - this.store = resourceServerStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realmModel) { - return origEntity -> new MapResourceServerAdapter(realmModel, origEntity, authorizationProvider.getStoreFactory()); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - @Override - public ResourceServer create(ClientModel client) { - LOG.tracef("create(%s)%s", client.getClientId(), getShortStackTrace()); - - String clientId = client.getId(); - if (clientId == null) return null; - - if (!StorageId.isLocalStorage(clientId)) { - throw new ModelException("Creating resource server from federated ClientModel not supported"); - } - - if (findByClient(client) != null) { - throw new ModelDuplicateException("Resource server assiciated with client : " + client.getClientId() + " already exists."); - } - - MapResourceServerEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapResourceServerEntity.class); - final RealmModel realm = client.getRealm(); - entity.setClientId(clientId); - entity.setRealmId(realm.getId()); - - entity = storeWithRealm(realm).create(entity); - return entity == null ? null : entityToAdapterFunc(realm).apply(entity); - } - - @Override - public void delete(ClientModel client) { - LOG.tracef("delete(%s)%s", client.getClientId(), getShortStackTrace()); - - ResourceServer resourceServer = findByClient(client); - if (resourceServer == null) return; - - final RealmModel realm = client.getRealm(); - authorizationProvider.getKeycloakSession().invalidate(RESOURCE_SERVER_BEFORE_REMOVE, realm, resourceServer); - - storeWithRealm(realm).delete(resourceServer.getId()); - - authorizationProvider.getKeycloakSession().invalidate(RESOURCE_SERVER_AFTER_REMOVE, resourceServer); - } - - @Override - public ResourceServer findById(RealmModel realm, String id) { - LOG.tracef("findById(%s)%s", id, getShortStackTrace()); - - if (id == null) { - return null; - } - - MapResourceServerEntity entity = storeWithRealm(realm).read(id); - return (entity == null || !Objects.equals(realm.getId(), entity.getRealmId())) ? null : entityToAdapterFunc(realm).apply(entity); - } - - @Override - public ResourceServer findByClient(ClientModel client) { - LOG.tracef("findByClient(%s) in realm(%s)%s", client.getClientId(), client.getRealm().getName(), getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, client.getRealm().getId()); - - final RealmModel realm = client.getRealm(); - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(entityToAdapterFunc(client.getRealm())) - .findFirst() - .orElse(null); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java deleted file mode 100644 index 5b1cc1e0f73..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2022 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.authorization; - -import org.jboss.logging.Logger; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.Resource.SearchableFields; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.adapter.MapResourceAdapter; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapResourceStore implements ResourceStore { - - private static final Logger LOG = Logger.getLogger(MapResourceStore.class); - private final AuthorizationProvider authorizationProvider; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapResourceStore(MapStorage resourceStore, AuthorizationProvider provider) { - this.authorizationProvider = provider; - this.store = resourceStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm, final ResourceServer resourceServer) { - return origEntity -> new MapResourceAdapter(realm, resourceServer, origEntity, authorizationProvider.getStoreFactory()); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private DefaultModelCriteria forRealmAndResourceServer(RealmModel realm, ResourceServer resourceServer) { - DefaultModelCriteria mcb = DefaultModelCriteria.criteria() - .compare(Resource.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return resourceServer == null - ? mcb - : mcb.compare(SearchableFields.RESOURCE_SERVER_ID, Operator.EQ, - resourceServer.getId()); - } - - @Override - public Resource create(ResourceServer resourceServer, String id, String name, String owner) { - LOG.tracef("create(%s, %s, %s, %s)%s", id, name, resourceServer, owner, getShortStackTrace()); - // @UniqueConstraint(columnNames = {"NAME", "RESOURCE_SERVER_ID", "OWNER"}) - RealmModel realm = resourceServer.getRealm(); - - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.NAME, Operator.EQ, name) - .compare(SearchableFields.OWNER, Operator.EQ, owner); - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("Resource with name '" + name + "' for " + resourceServer.getId() + " already exists for request owner " + owner); - } - - MapResourceEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapResourceEntity.class); - entity.setId(id); - entity.setName(name); - entity.setResourceServerId(resourceServer.getId()); - entity.setOwner(owner); - entity.setRealmId(realm.getId()); - - entity = storeWithRealm(realm).create(entity); - - return entity == null ? null : entityToAdapterFunc(realm, resourceServer).apply(entity); - } - - @Override - public void delete(RealmModel realm, String id) { - LOG.tracef("delete(%s)%s", id, getShortStackTrace()); - Resource resource = findById(realm, null, id); - if (resource == null) return; - - storeWithRealm(realm).delete(id); - } - - @Override - public Resource findById(RealmModel realm, ResourceServer resourceServer, String id) { - LOG.tracef("findById(%s, %s)%s", id, resourceServer, getShortStackTrace()); - - if (id == null) return null; - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.ID, Operator.EQ, id))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public void findByOwner(RealmModel realm, ResourceServer resourceServer, String ownerId, Consumer consumer) { - LOG.tracef("findByOwner(%s, %s, %s)%s", realm, resourceServer, resourceServer, ownerId, getShortStackTrace()); - - storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.OWNER, Operator.EQ, ownerId))) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(consumer); - } - - @Override - public List findByResourceServer(ResourceServer resourceServer) { - LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public List find(RealmModel realm, ResourceServer resourceServer, Map attributes, Integer firstResult, Integer maxResults) { - LOG.tracef("findByResourceServer(%s, %s, %s, %d, %d)%s", realm, resourceServer, attributes, firstResult, maxResults, getShortStackTrace()); - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer).and( - attributes.entrySet().stream() - .map(this::filterEntryToDefaultModelCriteria) - .toArray(DefaultModelCriteria[]::new) - ); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - private DefaultModelCriteria filterEntryToDefaultModelCriteria(Map.Entry entry) { - Resource.FilterOption name = entry.getKey(); - String[] value = entry.getValue(); - - DefaultModelCriteria mcb = criteria(); - switch (name) { - case ID: - case SCOPE_ID: - case OWNER: - case URI: - return mcb.compare(name.getSearchableModelField(), Operator.IN, Arrays.asList(value)); - case URI_NOT_NULL: - return mcb.compare(SearchableFields.URI, Operator.EXISTS); - case OWNER_MANAGED_ACCESS: - return mcb.compare(SearchableFields.OWNER_MANAGED_ACCESS, Operator.EQ, Boolean.valueOf(value[0])); - case EXACT_NAME: - return mcb.compare(SearchableFields.NAME, Operator.EQ, value[0]); - case NAME: - return mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + value[0] + "%"); - case TYPE: - return mcb.compare(SearchableFields.TYPE, Operator.ILIKE, "%" + value[0] + "%"); - default: - throw new IllegalArgumentException("Unsupported filter [" + name + "]"); - - } - } - - @Override - public void findByScopes(ResourceServer resourceServer, Set scopes, Consumer consumer) { - LOG.tracef("findByScope(%s, %s, %s)%s", scopes, resourceServer, consumer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes.stream().map(Scope::getId)))) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(consumer); - } - - @Override - public Resource findByName(ResourceServer resourceServer, String name, String ownerId) { - LOG.tracef("findByName(%s, %s, %s)%s", name, ownerId, resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.OWNER, Operator.EQ, ownerId) - .compare(SearchableFields.NAME, Operator.EQ, name))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public void findByType(ResourceServer resourceServer, String type, Consumer consumer) { - LOG.tracef("findByType(%s, %s, %s)%s", type, resourceServer, consumer, getShortStackTrace()); - RealmModel realm = authorizationProvider.getRealm(); - - storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.TYPE, Operator.EQ, type))) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(consumer); - } - - @Override - public void findByType(ResourceServer resourceServer, String type, String owner, Consumer consumer) { - LOG.tracef("findByType(%s, %s, %s, %s)%s", type, owner, resourceServer, consumer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.TYPE, Operator.EQ, type); - - if (owner != null) { - mcb = mcb.compare(SearchableFields.OWNER, Operator.EQ, owner); - } - - storeWithRealm(realm).read(withCriteria(mcb)) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(consumer); - } - - @Override - public void findByTypeInstance(ResourceServer resourceServer, String type, Consumer consumer) { - LOG.tracef("findByTypeInstance(%s, %s, %s)%s", type, resourceServer, consumer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.OWNER, Operator.NE, resourceServer.getClientId()) - .compare(SearchableFields.TYPE, Operator.EQ, type))) - .map(entityToAdapterFunc(realm, resourceServer)) - .forEach(consumer); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - public void preRemove(RealmModel realm, ResourceServer resourceServer) { - LOG.tracef("preRemove(%s, %s)%s", realm, resourceServer, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(forRealmAndResourceServer(resourceServer.getRealm(), resourceServer))); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java deleted file mode 100644 index 7e6c54e14ff..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2022 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.authorization; - -import org.jboss.logging.Logger; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.model.Scope.SearchableFields; -import org.keycloak.authorization.store.ScopeStore; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.adapter.MapScopeAdapter; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapScopeStore implements ScopeStore { - - private static final Logger LOG = Logger.getLogger(MapScopeStore.class); - private final AuthorizationProvider authorizationProvider; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapScopeStore(MapStorage scopeStore, AuthorizationProvider provider) { - this.authorizationProvider = provider; - this.store = scopeStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm, ResourceServer resourceServer) { - return origEntity -> new MapScopeAdapter(realm, resourceServer, origEntity, authorizationProvider.getStoreFactory()); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private DefaultModelCriteria forRealmAndResourceServer(RealmModel realm, ResourceServer resourceServer) { - DefaultModelCriteria mcb = DefaultModelCriteria.criteria() - .compare(Scope.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return resourceServer == null - ? mcb - : mcb.compare(SearchableFields.RESOURCE_SERVER_ID, Operator.EQ, - resourceServer.getId()); - } - - @Override - public Scope create(ResourceServer resourceServer, String id, String name) { - LOG.tracef("create(%s, %s, %s)%s", id, name, resourceServer, getShortStackTrace()); - - RealmModel realm = resourceServer.getRealm(); - // @UniqueConstraint(columnNames = {"NAME", "RESOURCE_SERVER_ID"}) - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.NAME, Operator.EQ, name); - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("Scope with name '" + name + "' for " + resourceServer.getId() + " already exists"); - } - - MapScopeEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapScopeEntity.class); - entity.setId(id); - entity.setName(name); - entity.setResourceServerId(resourceServer.getId()); - entity.setRealmId(resourceServer.getRealm().getId()); - - entity = storeWithRealm(realm).create(entity); - - return entity == null ? null : entityToAdapterFunc(realm, resourceServer).apply(entity); - } - - @Override - public void delete(RealmModel realm, String id) { - LOG.tracef("delete(%s)%s", id, getShortStackTrace()); - Scope scope = findById(realm, null, id); - if (scope == null) return; - - storeWithRealm(realm).delete(id); - } - - @Override - public Scope findById(RealmModel realm, ResourceServer resourceServer, String id) { - LOG.tracef("findById(%s, %s)%s", id, resourceServer, getShortStackTrace()); - - if (id == null) return null; - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer) - .compare(SearchableFields.ID, Operator.EQ, id))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public Scope findByName(ResourceServer resourceServer, String name) { - LOG.tracef("findByName(%s, %s)%s", name, resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer).compare(SearchableFields.NAME, - Operator.EQ, name))) - .findFirst() - .map(entityToAdapterFunc(realm, resourceServer)) - .orElse(null); - } - - @Override - public List findByResourceServer(ResourceServer resourceServer) { - LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); - RealmModel realm = resourceServer.getRealm(); - return storeWithRealm(realm).read(withCriteria(forRealmAndResourceServer(realm, resourceServer))) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - @Override - public List findByResourceServer(ResourceServer resourceServer, Map attributes, Integer firstResult, Integer maxResults) { - RealmModel realm = resourceServer.getRealm(); - DefaultModelCriteria mcb = forRealmAndResourceServer(realm, resourceServer); - - for (Scope.FilterOption filterOption : attributes.keySet()) { - String[] value = attributes.get(filterOption); - - switch (filterOption) { - case ID: - mcb = mcb.compare(Scope.SearchableFields.ID, Operator.IN, Arrays.asList(value)); - break; - case NAME: - mcb = mcb.compare(Scope.SearchableFields.NAME, Operator.ILIKE, "%" + value[0] + "%"); - break; - default: - throw new IllegalArgumentException("Unsupported filter [" + filterOption + "]"); - } - } - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm, resourceServer)) - .collect(Collectors.toList()); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - public void preRemove(RealmModel realm, ResourceServer resourceServer) { - LOG.tracef("preRemove(%s, %s)%s", realm, resourceServer, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(forRealmAndResourceServer(resourceServer.getRealm(), resourceServer))); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractPermissionTicketModel.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractPermissionTicketModel.java deleted file mode 100644 index b27cdcc5e4f..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractPermissionTicketModel.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.authorization.adapter; - -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractPermissionTicketModel implements PermissionTicket { - - protected final E entity; - protected final StoreFactory storeFactory; - - public AbstractPermissionTicketModel(E entity, StoreFactory storeFactory) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(storeFactory, "storeFactory"); - - this.storeFactory = storeFactory; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PermissionTicket)) return false; - - PermissionTicket that = (PermissionTicket) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractPolicyModel.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractPolicyModel.java deleted file mode 100644 index d262b7bcfa7..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractPolicyModel.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.authorization.adapter; - -import org.keycloak.authorization.model.AbstractAuthorizationModel; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractPolicyModel extends AbstractAuthorizationModel implements Policy { - - protected final E entity; - - public AbstractPolicyModel(E entity, StoreFactory storeFactory) { - super(storeFactory); - - Objects.requireNonNull(entity, "entity"); - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Policy)) return false; - - Policy that = (Policy) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractResourceModel.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractResourceModel.java deleted file mode 100644 index fb3301bfb14..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractResourceModel.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.authorization.adapter; - -import org.keycloak.authorization.model.AbstractAuthorizationModel; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractResourceModel extends AbstractAuthorizationModel implements Resource { - - protected final E entity; - - public AbstractResourceModel(E entity, StoreFactory storeFactory) { - super(storeFactory); - - Objects.requireNonNull(entity, "entity"); - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Resource)) return false; - - Resource that = (Resource) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractResourceServerModel.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractResourceServerModel.java deleted file mode 100644 index 5127647e37f..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractResourceServerModel.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.authorization.adapter; - -import org.keycloak.authorization.model.AbstractAuthorizationModel; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractResourceServerModel extends AbstractAuthorizationModel implements ResourceServer { - - protected final E entity; - - public AbstractResourceServerModel(E entity, StoreFactory storeFactory) { - super(storeFactory); - - Objects.requireNonNull(entity, "entity"); - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ResourceServer)) return false; - - ResourceServer that = (ResourceServer) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractScopeModel.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractScopeModel.java deleted file mode 100644 index b7550cf6141..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/AbstractScopeModel.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.authorization.adapter; - -import org.keycloak.authorization.model.AbstractAuthorizationModel; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractScopeModel extends AbstractAuthorizationModel implements Scope { - - protected final E entity; - - public AbstractScopeModel(E entity, StoreFactory storeFactory) { - super(storeFactory); - - Objects.requireNonNull(entity, "entity"); - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Scope)) return false; - - Scope that = (Scope) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java deleted file mode 100644 index 68835d08425..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2022 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.authorization.adapter; - - -import java.util.Objects; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.StoreFactory; - - -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy; - -public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel { - - private final RealmModel realm; - private ResourceServer resourceServer; - - public MapPermissionTicketAdapter(RealmModel realm, ResourceServer resourceServer, MapPermissionTicketEntity entity, StoreFactory storeFactory) { - super(entity, storeFactory); - - Objects.requireNonNull(realm, "realm"); - - this.realm = realm; - this.resourceServer = resourceServer; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getOwner() { - return entity.getOwner(); - } - - @Override - public String getRequester() { - return entity.getRequester(); - } - - @Override - public Resource getResource() { - return storeFactory.getResourceStore().findById(realm, getResourceServer(), entity.getResourceId()); - } - - @Override - public Scope getScope() { - if (entity.getScopeId() == null) return null; - return storeFactory.getScopeStore().findById(realm, getResourceServer(), entity.getScopeId()); - } - - @Override - public boolean isGranted() { - return entity.getGrantedTimestamp() != null; - } - - @Override - public Long getCreatedTimestamp() { - return entity.getCreatedTimestamp(); - } - - @Override - public Long getGrantedTimestamp() { - return entity.getGrantedTimestamp(); - } - - @Override - public void setGrantedTimestamp(Long millis) { - entity.setGrantedTimestamp(millis); - updatePolicy(this, storeFactory); - } - - @Override - public ResourceServer getResourceServer() { - if (resourceServer == null) { - resourceServer = storeFactory.getResourceServerStore().findById(realm, entity.getResourceServerId()); - } - return resourceServer; - } - - @Override - public Policy getPolicy() { - if (entity.getPolicyId() == null) return null; - return storeFactory.getPolicyStore().findById(realm, getResourceServer(), entity.getPolicyId()); - } - - @Override - public void setPolicy(Policy policy) { - if (policy != null) { - entity.setPolicyId(policy.getId()); - } - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), System.identityHashCode(this)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPolicyAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPolicyAdapter.java deleted file mode 100644 index 7910e1a6782..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPolicyAdapter.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2022 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.authorization.adapter; - -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.representations.idm.authorization.DecisionStrategy; -import org.keycloak.representations.idm.authorization.Logic; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -public class MapPolicyAdapter extends AbstractPolicyModel { - - private final RealmModel realm; - private ResourceServer resourceServer; - - public MapPolicyAdapter(RealmModel realm, ResourceServer resourceServer, MapPolicyEntity entity, StoreFactory storeFactory) { - super(entity, storeFactory); - Objects.requireNonNull(realm); - this.realm = realm; - this.resourceServer = resourceServer; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getType() { - return entity.getType(); - } - - @Override - public DecisionStrategy getDecisionStrategy() { - DecisionStrategy ds = entity.getDecisionStrategy(); - return ds == null ? DecisionStrategy.UNANIMOUS : ds; - } - - @Override - public void setDecisionStrategy(DecisionStrategy decisionStrategy) { - throwExceptionIfReadonly(); - entity.setDecisionStrategy(decisionStrategy); - } - - @Override - public Logic getLogic() { - Logic l = entity.getLogic(); - return l == null ? Logic.POSITIVE : l; - } - - @Override - public void setLogic(Logic logic) { - throwExceptionIfReadonly(); - entity.setLogic(logic); - } - - @Override - public Map getConfig() { - Map c = entity.getConfigs(); - return c == null ? Collections.emptyMap() : c; - } - - @Override - public void setConfig(Map config) { - throwExceptionIfReadonly(); - entity.setConfigs(config); - } - - @Override - public void removeConfig(String name) { - throwExceptionIfReadonly(); - entity.removeConfig(name); - } - - @Override - public void putConfig(String name, String value) { - throwExceptionIfReadonly(); - entity.setConfig(name, value); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - throwExceptionIfReadonly(); - entity.setName(name); - } - - @Override - public String getDescription() { - return entity.getDescription(); - } - - @Override - public void setDescription(String description) { - throwExceptionIfReadonly(); - entity.setDescription(description); - } - - @Override - public ResourceServer getResourceServer() { - if (resourceServer == null) { - resourceServer = storeFactory.getResourceServerStore().findById(realm, entity.getResourceServerId()); - } - return resourceServer; - } - - @Override - public Set getAssociatedPolicies() { - Set ids = entity.getAssociatedPolicyIds(); - ResourceServer resourceServer = getResourceServer(); - return ids == null ? Collections.emptySet() : ids.stream() - .map(policyId -> storeFactory.getPolicyStore().findById(realm, resourceServer, policyId)) - .collect(Collectors.toSet()); - } - - @Override - public Set getResources() { - Set ids = entity.getResourceIds(); - return ids == null ? Collections.emptySet() : ids.stream() - .map(resourceId -> storeFactory.getResourceStore().findById(realm, getResourceServer(), resourceId)) - .collect(Collectors.toSet()); - } - - @Override - public Set getScopes() { - Set ids = entity.getScopeIds(); - return ids == null ? Collections.emptySet() : ids.stream() - .map(scopeId -> storeFactory.getScopeStore().findById(realm, getResourceServer(), scopeId)) - .collect(Collectors.toSet()); - } - - @Override - public String getOwner() { - return entity.getOwner(); - } - - @Override - public void setOwner(String owner) { - throwExceptionIfReadonly(); - entity.setOwner(owner); - } - - @Override - public void addScope(Scope scope) { - throwExceptionIfReadonly(); - entity.addScopeId(scope.getId()); - } - - @Override - public void removeScope(Scope scope) { - throwExceptionIfReadonly(); - entity.removeScopeId(scope.getId()); - } - - @Override - public void addAssociatedPolicy(Policy associatedPolicy) { - throwExceptionIfReadonly(); - entity.addAssociatedPolicyId(associatedPolicy.getId()); - } - - @Override - public void removeAssociatedPolicy(Policy associatedPolicy) { - throwExceptionIfReadonly(); - entity.removeAssociatedPolicyId(associatedPolicy.getId()); - } - - @Override - public void addResource(Resource resource) { - throwExceptionIfReadonly(); - entity.addResourceId(resource.getId()); - } - - @Override - public void removeResource(Resource resource) { - throwExceptionIfReadonly(); - entity.removeResourceId(resource.getId()); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), System.identityHashCode(this)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java deleted file mode 100644 index 1c3ec259fd7..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2022 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.authorization.adapter; - -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.PermissionTicketStore; -import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.authorization.store.StoreFactory; - -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -public class MapResourceAdapter extends AbstractResourceModel { - - private final RealmModel realm; - private ResourceServer resourceServer; - - public MapResourceAdapter(RealmModel realm, ResourceServer resourceServer, MapResourceEntity entity, StoreFactory storeFactory) { - super(entity, storeFactory); - Objects.requireNonNull(realm); - this.realm = realm; - this.resourceServer = resourceServer; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - throwExceptionIfReadonly(); - entity.setName(name); - } - - @Override - public String getDisplayName() { - return entity.getDisplayName(); - } - - @Override - public void setDisplayName(String name) { - throwExceptionIfReadonly(); - entity.setDisplayName(name); - } - - @Override - public Set getUris() { - Set uris = entity.getUris(); - return uris == null ? Collections.emptySet() : entity.getUris(); - } - - @Override - public void updateUris(Set uri) { - throwExceptionIfReadonly(); - entity.setUris(uri); - } - - @Override - public String getType() { - return entity.getType(); - } - - @Override - public void setType(String type) { - throwExceptionIfReadonly(); - entity.setType(type); - } - - @Override - public List getScopes() { - Set ids = entity.getScopeIds(); - ResourceServer resourceServer = getResourceServer(); - return ids == null ? Collections.emptyList() : ids.stream() - .map(id -> storeFactory - .getScopeStore().findById(realm, resourceServer, id)) - .collect(Collectors.toList()); - } - - @Override - public String getIconUri() { - return entity.getIconUri(); - } - - @Override - public void setIconUri(String iconUri) { - throwExceptionIfReadonly(); - entity.setIconUri(iconUri); - } - - @Override - public ResourceServer getResourceServer() { - if (resourceServer == null) { - resourceServer = storeFactory.getResourceServerStore().findById(realm, entity.getResourceServerId()); - } - return resourceServer; - } - - @Override - public String getOwner() { - return entity.getOwner(); - } - - @Override - public boolean isOwnerManagedAccess() { - Boolean isOMA = entity.isOwnerManagedAccess(); - return isOMA == null ? false : isOMA; - } - - @Override - public void setOwnerManagedAccess(boolean ownerManagedAccess) { - throwExceptionIfReadonly(); - entity.setOwnerManagedAccess(ownerManagedAccess); - } - - @Override - public void updateScopes(Set scopes) { - throwExceptionIfReadonly(); - - PermissionTicketStore permissionStore = storeFactory.getPermissionTicketStore(); - PolicyStore policyStore = storeFactory.getPolicyStore(); - - for (Scope scope : getScopes()) { - if (!scopes.contains(scope)) { - // The scope^ was removed from the Resource - - // Remove permission tickets based on the scope - List permissions = permissionStore.findByScope(getResourceServer(), scope); - for (PermissionTicket permission : permissions) { - permissionStore.delete(realm, permission.getId()); - } - - // Remove the scope from each Policy for this Resource - policyStore.findByResource(getResourceServer(), this, policy -> policy.removeScope(scope)); - } - } - - entity.setScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toSet())); - } - - @Override - public Map> getAttributes() { - Map> attrs = entity.getAttributes(); - return attrs == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(attrs)); - } - - @Override - public String getSingleAttribute(String name) { - List attributeValues = entity.getAttribute(name); - return attributeValues == null || attributeValues.isEmpty() ? null : attributeValues.get(0); - } - - @Override - public List getAttribute(String name) { - return entity.getAttribute(name); - } - - @Override - public void setAttribute(String name, List values) { - throwExceptionIfReadonly(); - entity.setAttribute(name, values); - } - - @Override - public void removeAttribute(String name) { - throwExceptionIfReadonly(); - entity.removeAttribute(name); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), System.identityHashCode(this)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java deleted file mode 100644 index 42a49d8f2f1..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.authorization.adapter; - - -import java.util.Objects; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.representations.idm.authorization.DecisionStrategy; -import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; - -public class MapResourceServerAdapter extends AbstractResourceServerModel { - - private final RealmModel realmModel; - - public MapResourceServerAdapter(RealmModel realmModel, MapResourceServerEntity entity, StoreFactory storeFactory) { - super(entity, storeFactory); - Objects.requireNonNull(realmModel); - this.realmModel = realmModel; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public boolean isAllowRemoteResourceManagement() { - Boolean isARRM = entity.isAllowRemoteResourceManagement(); - return isARRM == null ? false : isARRM; - } - - @Override - public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) { - throwExceptionIfReadonly(); - entity.setAllowRemoteResourceManagement(allowRemoteResourceManagement); - } - - @Override - public PolicyEnforcementMode getPolicyEnforcementMode() { - PolicyEnforcementMode pem = entity.getPolicyEnforcementMode(); - return pem == null ? PolicyEnforcementMode.ENFORCING : pem; - } - - @Override - public void setPolicyEnforcementMode(PolicyEnforcementMode enforcementMode) { - throwExceptionIfReadonly(); - entity.setPolicyEnforcementMode(enforcementMode); - } - - @Override - public void setDecisionStrategy(DecisionStrategy decisionStrategy) { - throwExceptionIfReadonly(); - entity.setDecisionStrategy(decisionStrategy); - } - - @Override - public DecisionStrategy getDecisionStrategy() { - DecisionStrategy ds = entity.getDecisionStrategy(); - return ds == null ? DecisionStrategy.UNANIMOUS : ds; - } - - @Override - public String getClientId() { - return entity.getClientId(); - } - - @Override - public RealmModel getRealm() { - return realmModel; - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), System.identityHashCode(this)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapScopeAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapScopeAdapter.java deleted file mode 100644 index 3dc20c2f648..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapScopeAdapter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2022 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.authorization.adapter; - - -import java.util.Objects; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; - -public class MapScopeAdapter extends AbstractScopeModel { - - private final RealmModel realm; - private ResourceServer resourceServer; - - public MapScopeAdapter(RealmModel realm, ResourceServer resourceServer, MapScopeEntity entity, StoreFactory storeFactory) { - super(entity, storeFactory); - Objects.requireNonNull(realm); - this.realm = realm; - this.resourceServer = resourceServer; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - throwExceptionIfReadonly(); - entity.setName(name); - } - - @Override - public String getDisplayName() { - return entity.getDisplayName(); - } - - @Override - public void setDisplayName(String name) { - throwExceptionIfReadonly(); - entity.setDisplayName(name); - } - - @Override - public String getIconUri() { - return entity.getIconUri(); - } - - @Override - public void setIconUri(String iconUri) { - throwExceptionIfReadonly(); - entity.setIconUri(iconUri); - } - - @Override - public ResourceServer getResourceServer() { - if (resourceServer == null) { - resourceServer = storeFactory.getResourceServerStore().findById(realm, entity.getResourceServerId()); - } - return resourceServer; - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), System.identityHashCode(this)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java deleted file mode 100644 index 0da8549b0f6..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2022 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.authorization.entity; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity.AbstractMapPermissionTicketEntity" -) -@DeepCloner.Root -public interface MapPermissionTicketEntity extends UpdatableEntity, AbstractEntity { - - public abstract class AbstractMapPermissionTicketEntity extends UpdatableEntity.Impl implements MapPermissionTicketEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getOwner(); - void setOwner(String owner); - - String getRequester(); - void setRequester(String requester); - - Long getCreatedTimestamp(); - void setCreatedTimestamp(Long createdTimestamp); - - Long getGrantedTimestamp(); - void setGrantedTimestamp(Long grantedTimestamp); - - String getResourceId(); - void setResourceId(String resourceId); - - String getScopeId(); - void setScopeId(String scopeId); - - String getResourceServerId(); - void setResourceServerId(String resourceServerId); - - String getPolicyId(); - void setPolicyId(String policyId); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java deleted file mode 100644 index d69cf582798..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 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.authorization.entity; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.representations.idm.authorization.DecisionStrategy; -import org.keycloak.representations.idm.authorization.Logic; - -import java.util.Map; -import java.util.Set; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.authorization.entity.MapPolicyEntity.AbstractMapPolicyEntity" -) -@DeepCloner.Root -public interface MapPolicyEntity extends UpdatableEntity, AbstractEntity { - - public abstract class AbstractMapPolicyEntity extends UpdatableEntity.Impl implements MapPolicyEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getName(); - void setName(String name); - - String getDescription(); - void setDescription(String description); - - String getType(); - void setType(String type); - - DecisionStrategy getDecisionStrategy(); - void setDecisionStrategy(DecisionStrategy decisionStrategy); - - Logic getLogic(); - void setLogic(Logic logic); - - Map getConfigs(); - void setConfigs(Map config); - String getConfig(String name); - void setConfig(String name, String value); - void removeConfig(String name); - - String getResourceServerId(); - void setResourceServerId(String resourceServerId); - - Set getAssociatedPolicyIds(); - void addAssociatedPolicyId(String policyId); - void removeAssociatedPolicyId(String policyId); - - Set getResourceIds(); - void addResourceId(String resourceId); - void removeResourceId(String resourceId); - - Set getScopeIds(); - void addScopeId(String scopeId); - void removeScopeId(String scopeId); - - String getOwner(); - void setOwner(String owner); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java deleted file mode 100644 index e74a21db524..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2022 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.authorization.entity; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Set; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.authorization.entity.MapResourceEntity.AbstractMapResourceEntity" -) -@DeepCloner.Root -public interface MapResourceEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes { - - public abstract class AbstractMapResourceEntity extends UpdatableEntity.Impl implements MapResourceEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getName(); - void setName(String name); - - String getDisplayName(); - void setDisplayName(String displayName); - - Set getUris(); - void setUris(Set uris); - - String getType(); - void setType(String type); - - String getIconUri(); - void setIconUri(String iconUri); - - String getOwner(); - void setOwner(String owner); - - Boolean isOwnerManagedAccess(); - void setOwnerManagedAccess(Boolean ownerManagedAccess); - - void setResourceServerId(String resourceServerId); - String getResourceServerId(); - - Set getScopeIds(); - void setScopeIds(Set scopeIds); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java deleted file mode 100644 index 72774268e8e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2022 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.authorization.entity; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.representations.idm.authorization.DecisionStrategy; -import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.authorization.entity.MapResourceServerEntity.AbstractMapResourceServerEntity" -) -@DeepCloner.Root -public interface MapResourceServerEntity extends UpdatableEntity, AbstractEntity { - - public abstract class AbstractMapResourceServerEntity extends UpdatableEntity.Impl implements MapResourceServerEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getClientId(); - void setClientId(String clientId); - - Boolean isAllowRemoteResourceManagement(); - void setAllowRemoteResourceManagement(Boolean allowRemoteResourceManagement); - - PolicyEnforcementMode getPolicyEnforcementMode(); - void setPolicyEnforcementMode(PolicyEnforcementMode policyEnforcementMode); - - DecisionStrategy getDecisionStrategy(); - void setDecisionStrategy(DecisionStrategy decisionStrategy); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java deleted file mode 100644 index ef1ffc2059b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2022 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.authorization.entity; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.authorization.entity.MapScopeEntity.AbstractMapScopeEntity" -) -@DeepCloner.Root -public interface MapScopeEntity extends UpdatableEntity, AbstractEntity { - - public abstract class AbstractMapScopeEntity extends UpdatableEntity.Impl implements MapScopeEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getName(); - void setName(String name); - - String getDisplayName(); - void setDisplayName(String displayName); - - String getIconUri(); - void setIconUri(String iconUri); - - String getResourceServerId(); - void setResourceServerId(String resourceServerId); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java b/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java deleted file mode 100644 index 364f0d52b40..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java +++ /dev/null @@ -1,125 +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 java.util.Collections; -import java.util.Map; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.utils.RoleUtils; - -import java.util.Objects; -import java.util.Set; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -public abstract class AbstractClientModel implements ClientModel { - - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractClientModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public void addClientScopes(Set clientScopes, boolean defaultScope) { - session.clients().addClientScopes(getRealm(), this, clientScopes, defaultScope); - } - - @Override - public void addClientScope(ClientScopeModel clientScope, boolean defaultScope) { - addClientScopes(Collections.singleton(clientScope), defaultScope); - } - - @Override - public void removeClientScope(ClientScopeModel clientScope) { - session.clients().removeClientScope(getRealm(), this, clientScope); - } - - @Override - public Map getClientScopes(boolean defaultScope) { - return session.clients().getClientScopes(getRealm(), this, defaultScope); - } - - @Override - public Stream getRealmScopeMappingsStream() { - return getScopeMappingsStream().filter(r -> RoleUtils.isRealmRole(r, realm)); - } - - @Override - public RoleModel getRole(String name) { - return session.roles().getClientRole(this, name); - } - - @Override - public RoleModel addRole(String name) { - return session.roles().addClientRole(this, name); - } - - @Override - public RoleModel addRole(String id, String name) { - return session.roles().addClientRole(this, id, name); - } - - @Override - public boolean removeRole(RoleModel role) { - return session.roles().removeRole(role); - } - - @Override - public Stream getRolesStream() { - return session.roles().getClientRolesStream(this, null, null); - } - - @Override - public Stream getRolesStream(Integer firstResult, Integer maxResults) { - return session.roles().getClientRolesStream(this, firstResult, maxResults); - } - - @Override - public Stream searchForRolesStream(String search, Integer first, Integer max) { - return session.roles().searchForClientRolesStream(this, search, first, max); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ClientModel)) return false; - - ClientModel that = (ClientModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} 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 deleted file mode 100644 index 9b156bd9571..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.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.jboss.logging.Logger; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.TimeAdapter; -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; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -public abstract class MapClientAdapter extends AbstractClientModel implements ClientModel { - - private static final Logger LOG = Logger.getLogger(MapClientAdapter.class); - private final MapProtocolMapperUtils pmUtils; - - public MapClientAdapter(KeycloakSession session, RealmModel realm, MapClientEntity entity) { - super(session, realm, entity); - pmUtils = MapProtocolMapperUtils.instanceFor(safeGetProtocol()); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getClientId() { - return entity.getClientId(); - } - - @Override - public void setClientId(String clientId) { - entity.setClientId(clientId); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - entity.setName(name); - } - - @Override - public String getDescription() { - return entity.getDescription(); - } - - @Override - public void setDescription(String description) { - entity.setDescription(description); - } - - @Override - public boolean isEnabled() { - final Boolean enabled = entity.isEnabled(); - return enabled == null ? false : enabled; - } - - @Override - public void setEnabled(boolean enabled) { - entity.setEnabled(enabled); - } - - @Override - public boolean isAlwaysDisplayInConsole() { - final Boolean alwaysDisplayInConsole = entity.isAlwaysDisplayInConsole(); - return alwaysDisplayInConsole == null ? false : alwaysDisplayInConsole; - } - - @Override - public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) { - entity.setAlwaysDisplayInConsole(alwaysDisplayInConsole); - } - - @Override - public boolean isSurrogateAuthRequired() { - final Boolean surrogateAuthRequired = entity.isSurrogateAuthRequired(); - return surrogateAuthRequired == null ? false : surrogateAuthRequired; - } - - @Override - public void setSurrogateAuthRequired(boolean surrogateAuthRequired) { - entity.setSurrogateAuthRequired(surrogateAuthRequired); - } - - @Override - public Set getWebOrigins() { - final Set webOrigins = entity.getWebOrigins(); - return webOrigins == null ? Collections.emptySet() : webOrigins; - } - - @Override - public void setWebOrigins(Set webOrigins) { - entity.setWebOrigins(webOrigins); - } - - @Override - public void addWebOrigin(String webOrigin) { - entity.addWebOrigin(webOrigin); - } - - @Override - public void removeWebOrigin(String webOrigin) { - entity.removeWebOrigin(webOrigin); - } - - @Override - public Set getRedirectUris() { - final Set redirectUris = entity.getRedirectUris(); - return redirectUris == null ? Collections.emptySet() : redirectUris; - } - - @Override - public void setRedirectUris(Set redirectUris) { - entity.setRedirectUris(redirectUris); - } - - @Override - public void addRedirectUri(String redirectUri) { - entity.addRedirectUri(redirectUri); - } - - @Override - public void removeRedirectUri(String redirectUri) { - entity.removeRedirectUri(redirectUri); - } - - @Override - public String getManagementUrl() { - return entity.getManagementUrl(); - } - - @Override - public void setManagementUrl(String url) { - entity.setManagementUrl(url); - } - - @Override - public String getRootUrl() { - return entity.getRootUrl(); - } - - @Override - public void setRootUrl(String url) { - entity.setRootUrl(url); - } - - @Override - public String getBaseUrl() { - return entity.getBaseUrl(); - } - - @Override - public void setBaseUrl(String url) { - entity.setBaseUrl(url); - } - - @Override - public boolean isBearerOnly() { - final Boolean bearerOnly = entity.isBearerOnly(); - return bearerOnly == null ? false : bearerOnly; - } - - @Override - public void setBearerOnly(boolean only) { - entity.setBearerOnly(only); - } - - @Override - public String getClientAuthenticatorType() { - return entity.getClientAuthenticatorType(); - } - - @Override - public void setClientAuthenticatorType(String clientAuthenticatorType) { - entity.setClientAuthenticatorType(clientAuthenticatorType); - } - - @Override - public boolean validateSecret(String secret) { - return MessageDigest.isEqual(secret.getBytes(), entity.getSecret().getBytes()); - } - - @Override - public String getSecret() { - return entity.getSecret(); - } - - @Override - public void setSecret(String secret) { - entity.setSecret(secret); - } - - @Override - public int getNodeReRegistrationTimeout() { - final Integer nodeReRegistrationTimeout = entity.getNodeReRegistrationTimeout(); - return nodeReRegistrationTimeout == null ? 0 : nodeReRegistrationTimeout; - } - - @Override - public void setNodeReRegistrationTimeout(int timeout) { - entity.setNodeReRegistrationTimeout(timeout); - } - - @Override - public String getRegistrationToken() { - return entity.getRegistrationToken(); - } - - @Override - public void setRegistrationToken(String registrationToken) { - entity.setRegistrationToken(registrationToken); - } - - @Override - public String getProtocol() { - return entity.getProtocol(); - } - - @Override - public void setProtocol(String protocol) { - if (!Objects.equals(entity.getProtocol(), protocol)) { - entity.setProtocol(protocol); - session.getKeycloakSessionFactory().publish((ClientModel.ClientProtocolUpdatedEvent) () -> MapClientAdapter.this); - } - } - - @Override - public void setAttribute(String name, String value) { - boolean valueUndefined = value == null || "".equals(value.trim()); - - if (valueUndefined) { - removeAttribute(name); - return; - } - - entity.setAttribute(name, Collections.singletonList(value)); - } - - @Override - public void removeAttribute(String name) { - entity.removeAttribute(name); - } - - @Override - public String getAttribute(String name) { - List attribute = entity.getAttribute(name); - if (attribute == null || attribute.isEmpty()) return null; - return attribute.get(0); - } - - @Override - public Map getAttributes() { - 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; - } else if (entry.getValue().size() > 1) { - // This could be caused by an inconsistency in the storage, a programming error, - // or a downgrade from a future version of Keycloak that already supports multi-valued attributes. - // The caller will not see the other values, and when this entity is later updated, the additional values will be lost. - LOG.warnf("Client '%s' realm '%s' has attribute '%s' with %d values, retrieving only the first", getClientId(), getRealm().getName(), entry.getKey(), - entry.getValue().size()); - } - return entry.getValue().get(0); - }) - ); - } - - @Override - public String getAuthenticationFlowBindingOverride(String binding) { - return entity.getAuthenticationFlowBindingOverride(binding); - } - - @Override - public Map getAuthenticationFlowBindingOverrides() { - final Map authenticationFlowBindingOverrides = entity.getAuthenticationFlowBindingOverrides(); - return authenticationFlowBindingOverrides == null ? Collections.emptyMap() : authenticationFlowBindingOverrides; - } - - @Override - public void removeAuthenticationFlowBindingOverride(String binding) { - entity.removeAuthenticationFlowBindingOverride(binding); - } - - @Override - public void setAuthenticationFlowBindingOverride(String binding, String flowId) { - entity.setAuthenticationFlowBindingOverride(binding, flowId); - } - - @Override - public boolean isFrontchannelLogout() { - final Boolean frontchannelLogout = entity.isFrontchannelLogout(); - return frontchannelLogout == null ? false : frontchannelLogout; - } - - @Override - public void setFrontchannelLogout(boolean flag) { - entity.setFrontchannelLogout(flag); - } - - @Override - public boolean isFullScopeAllowed() { - final Boolean fullScopeAllowed = entity.isFullScopeAllowed(); - return fullScopeAllowed == null ? false : fullScopeAllowed; - } - - @Override - public void setFullScopeAllowed(boolean value) { - entity.setFullScopeAllowed(value); - } - - @Override - public boolean isPublicClient() { - final Boolean publicClient = entity.isPublicClient(); - return publicClient == null ? false : publicClient; - } - - @Override - public void setPublicClient(boolean flag) { - entity.setPublicClient(flag); - } - - @Override - public boolean isConsentRequired() { - final Boolean consentRequired = entity.isConsentRequired(); - return consentRequired == null ? false : consentRequired; - } - - @Override - public void setConsentRequired(boolean consentRequired) { - entity.setConsentRequired(consentRequired); - } - - @Override - public boolean isStandardFlowEnabled() { - final Boolean standardFlowEnabled = entity.isStandardFlowEnabled(); - return standardFlowEnabled == null ? false : standardFlowEnabled; - } - - @Override - public void setStandardFlowEnabled(boolean standardFlowEnabled) { - entity.setStandardFlowEnabled(standardFlowEnabled); - } - - @Override - public boolean isImplicitFlowEnabled() { - final Boolean implicitFlowEnabled = entity.isImplicitFlowEnabled(); - return implicitFlowEnabled == null ? false : implicitFlowEnabled; - } - - @Override - public void setImplicitFlowEnabled(boolean implicitFlowEnabled) { - entity.setImplicitFlowEnabled(implicitFlowEnabled); - } - - @Override - public boolean isDirectAccessGrantsEnabled() { - final Boolean directAccessGrantsEnabled = entity.isDirectAccessGrantsEnabled(); - return directAccessGrantsEnabled == null ? false : directAccessGrantsEnabled; - } - - @Override - public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) { - entity.setDirectAccessGrantsEnabled(directAccessGrantsEnabled); - } - - @Override - public boolean isServiceAccountsEnabled() { - final Boolean serviceAccountsEnabled = entity.isServiceAccountsEnabled(); - return serviceAccountsEnabled == null ? false : serviceAccountsEnabled; - } - - @Override - public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) { - entity.setServiceAccountsEnabled(serviceAccountsEnabled); - } - - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public int getNotBefore() { - final Long notBefore = entity.getNotBefore(); - return notBefore == null ? 0 : TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(notBefore); - } - - @Override - public void setNotBefore(int notBefore) { - entity.setNotBefore(TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(notBefore)); - } - - /*************** Scopes mappings ****************/ - - @Override - public Stream getScopeMappingsStream() { - final Collection scopeMappings = this.entity.getScopeMappings(); - return scopeMappings == null ? Stream.empty() : scopeMappings.stream() - .map(realm::getRoleById) - .filter(Objects::nonNull); - } - - @Override - public void addScopeMapping(RoleModel role) { - final String id = role == null ? null : role.getId(); - if (id != null) { - this.entity.addScopeMapping(id); - } - } - - @Override - public void deleteScopeMapping(RoleModel role) { - final String id = role == null ? null : role.getId(); - if (id != null) { - this.entity.removeScopeMapping(id); - } - } - - @Override - public boolean hasDirectScope(RoleModel role) { - final String id = role == null ? null : role.getId(); - final Collection scopeMappings = this.entity.getScopeMappings(); - if (id != null && scopeMappings != null && scopeMappings.contains(id)) { - return true; - } - - return getRolesStream().anyMatch(r -> (Objects.equals(r, role))); - } - - @Override - public boolean hasScope(RoleModel role) { - if (isFullScopeAllowed()) return true; - - final String id = role == null ? null : role.getId(); - final Collection scopeMappings = this.entity.getScopeMappings(); - if (id != null && scopeMappings != null && scopeMappings.contains(id)) { - return true; - } - - if (getScopeMappingsStream().anyMatch(r -> r.hasRole(role))) { - return true; - } - - return getRolesStream().anyMatch(r -> (Objects.equals(r, role) || r.hasRole(role))); - } - - /*************** Protocol mappers ****************/ - - private String safeGetProtocol() { - return entity.getProtocol() == null ? "openid-connect" : entity.getProtocol(); - } - - @Override - public Stream getProtocolMappersStream() { - final Set protocolMappers = entity.getProtocolMappers(); - return protocolMappers == null ? Stream.empty() : protocolMappers.stream().distinct().map(pmUtils::toModel); - } - - @Override - public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) { - if (model == null) { - return null; - } - - MapProtocolMapperEntity pm = MapProtocolMapperUtils.fromModel(model); - if (pm.getId() == null) { - String id = KeycloakModelUtils.generateId(); - pm.setId(id); - } - if (model.getConfig() == null) { - pm.setConfig(new HashMap<>()); - } - - entity.addProtocolMapper(pm); - return pmUtils.toModel(pm); - } - - @Override - public void removeProtocolMapper(ProtocolMapperModel mapping) { - final String id = mapping == null ? null : mapping.getId(); - if (id != null) { - entity.removeProtocolMapper(id); - } - } - - @Override - public void updateProtocolMapper(ProtocolMapperModel mapping) { - final String id = mapping == null ? null : mapping.getId(); - if (id != null) { - entity.getProtocolMapper(id).ifPresent((pmEntity) -> { - entity.removeProtocolMapper(id); - addProtocolMapper(mapping); - }); - } - } - - @Override - public ProtocolMapperModel getProtocolMapperById(String id) { - return entity.getProtocolMapper(id).map(pmUtils::toModel).orElse(null); - } - - @Override - public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) { - final Set protocolMappers = entity.getProtocolMappers(); - if (! Objects.equals(protocol, safeGetProtocol())) { - return null; - } - return protocolMappers == null ? null : protocolMappers.stream() - .filter(pm -> Objects.equals(pm.getName(), name)) - .map(pmUtils::toModel) - .findAny() - .orElse(null); - } - - @Override - public String toString() { - return String.format("%s@%08x", getClientId(), System.identityHashCode(this)); - } -} 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 deleted file mode 100644 index e9624928a3a..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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.client; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; - -/** - * - * @author hmlnarik - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity" -) -@DeepCloner.Root -public interface MapClientEntity extends AbstractEntity, UpdatableEntity, EntityWithAttributes { - - public abstract class AbstractClientEntity extends UpdatableEntity.Impl implements MapClientEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - @Override - public boolean isUpdated() { - return this.updated - || Optional.ofNullable(getProtocolMappers()).orElseGet(Collections::emptySet).stream().anyMatch(MapProtocolMapperEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - Optional.ofNullable(getProtocolMappers()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - } - - @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); - - Optional getProtocolMapper(String id); - Set getProtocolMappers(); - void addProtocolMapper(MapProtocolMapperEntity 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); - - String getAuthenticationFlowBindingOverride(String binding); - Map getAuthenticationFlowBindingOverrides(); - void removeAuthenticationFlowBindingOverride(String binding); - void setAuthenticationFlowBindingOverride(String binding, String flowId); - - String getBaseUrl(); - - String getClientAuthenticatorType(); - - String getClientId(); - - String getDescription(); - - String getManagementUrl(); - - String getName(); - - Integer getNodeReRegistrationTimeout(); - - Long getNotBefore(); - - String getProtocol(); - - String getRealmId(); - - String getRegistrationToken(); - - String getRootUrl(); - - Set getScope(); - - String getSecret(); - - Boolean isAlwaysDisplayInConsole(); - - Boolean isBearerOnly(); - - Boolean isConsentRequired(); - - Boolean isDirectAccessGrantsEnabled(); - - Boolean isEnabled(); - - Boolean isFrontchannelLogout(); - - Boolean isFullScopeAllowed(); - - Boolean isImplicitFlowEnabled(); - - Boolean isPublicClient(); - - Boolean isServiceAccountsEnabled(); - - Boolean isStandardFlowEnabled(); - - Boolean isSurrogateAuthRequired(); - - void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole); - - void setBaseUrl(String baseUrl); - - void setBearerOnly(Boolean bearerOnly); - - void setClientAuthenticatorType(String clientAuthenticatorType); - - void setClientId(String clientId); - - void setConsentRequired(Boolean consentRequired); - - void setDescription(String description); - - void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled); - - void setEnabled(Boolean enabled); - - void setFrontchannelLogout(Boolean frontchannelLogout); - - void setFullScopeAllowed(Boolean fullScopeAllowed); - - void setImplicitFlowEnabled(Boolean implicitFlowEnabled); - - void setManagementUrl(String managementUrl); - - void setName(String name); - - void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout); - - void setNotBefore(Long notBefore); - - void setProtocol(String protocol); - - void setPublicClient(Boolean publicClient); - - void setRealmId(String realmId); - - void setRegistrationToken(String registrationToken); - - void setRootUrl(String rootUrl); - - void setScope(Set scope); - - void setSecret(String secret); - - void setServiceAccountsEnabled(Boolean serviceAccountsEnabled); - - void setStandardFlowEnabled(Boolean standardFlowEnabled); - - void setSurrogateAuthRequired(Boolean surrogateAuthRequired); - -} 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 deleted file mode 100644 index 8245c4a5208..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java +++ /dev/null @@ -1,382 +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 java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.jboss.logging.Logger; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientModel.ClientUpdatedEvent; -import org.keycloak.models.ClientModel.SearchableFields; -import org.keycloak.models.ClientProvider; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapClientProvider implements ClientProvider { - - private static final Logger LOG = Logger.getLogger(MapClientProvider.class); - private final KeycloakSession session; - final MapStorage store; - private final ConcurrentMap> clientRegisteredNodesStore; - private final boolean storeHasRealmId; - - public MapClientProvider(KeycloakSession session, MapStorage clientStore, ConcurrentMap> clientRegisteredNodesStore) { - this.session = session; - this.clientRegisteredNodesStore = clientRegisteredNodesStore; - this.store = clientStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private ClientUpdatedEvent clientUpdatedEvent(ClientModel c) { - return new ClientModel.ClientUpdatedEvent() { - @Override - public ClientModel getUpdatedClient() { - return c; - } - - @Override - public KeycloakSession getKeycloakSession() { - return session; - } - }; - } - - private Function entityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - - return origEntity -> new MapClientAdapter(session, realm, origEntity) { - @Override - public void updateClient() { - LOG.tracef("updateClient(%s)%s", realm, origEntity.getId(), getShortStackTrace()); - session.getKeycloakSessionFactory().publish(clientUpdatedEvent(this)); - } - - /** This is runtime information and should have never been part of the adapter */ - @Override - public Map getRegisteredNodes() { - return Collections.unmodifiableMap(getMapForEntity() - .entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(e.getValue()))) - ); - } - - @Override - public void registerNode(String nodeHost, int registrationTime) { - getMapForEntity().put(nodeHost, TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(registrationTime)); - } - - @Override - public void unregisterNode(String nodeHost) { - getMapForEntity().remove(nodeHost); - } - - private ConcurrentMap getMapForEntity() { - return clientRegisteredNodesStore.computeIfAbsent(entity.getId(), k -> new ConcurrentHashMap<>()); - } - - }; - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private Predicate entityRealmFilter(RealmModel realm) { - if (realm == null || realm.getId() == null) { - return c -> false; - } - String realmId = realm.getId(); - return entity -> Objects.equals(realmId, entity.getRealmId()); - } - - @Override - public Stream getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.CLIENT_ID)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream getClientsStream(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.CLIENT_ID, ASCENDING)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public ClientModel addClient(RealmModel realm, String id, String clientId) { - LOG.tracef("addClient(%s, %s, %s)%s", realm, id, clientId, getShortStackTrace()); - - if (id != null && storeWithRealm(realm).exists(id)) { - throw new ModelDuplicateException("Client with same id exists: " + id); - } - if (clientId != null && getClientByClientId(realm, clientId) != null) { - throw new ModelDuplicateException("Client with same clientId in realm " + realm.getName() + " exists: " + clientId); - } - - MapClientEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapClientEntity.class); - entity.setId(id); - entity.setRealmId(realm.getId()); - entity.setClientId(clientId); - entity.setEnabled(true); - entity.setStandardFlowEnabled(true); - entity = storeWithRealm(realm).create(entity); - if (clientId == null) { - clientId = entity.getId(); - entity.setClientId(clientId); - } - final ClientModel resource = entityToAdapterFunc(realm).apply(entity); - - // TODO: Sending an event should be extracted to store layer - session.getKeycloakSessionFactory().publish((ClientModel.ClientCreationEvent) () -> resource); - resource.updateClient(); // This is actualy strange contract - it should be the store code to call updateClient - - return resource; - } - - @Override - public Stream getAlwaysDisplayInConsoleClientsStream(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ALWAYS_DISPLAY_IN_CONSOLE, Operator.EQ, Boolean.TRUE); - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.CLIENT_ID, ASCENDING)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public void removeClients(RealmModel realm) { - LOG.tracef("removeClients(%s)%s", realm, getShortStackTrace()); - - getClientsStream(realm) - .map(ClientModel::getId) - .collect(Collectors.toSet()) // This is necessary to read out all the client IDs before removing the clients - .forEach(cid -> removeClient(realm, cid)); - } - - @Override - public boolean removeClient(RealmModel realm, String id) { - if (id == null) return false; - - LOG.tracef("removeClient(%s, %s)%s", realm, id, getShortStackTrace()); - - final ClientModel client = getClientById(realm, id); - if (client == null) return false; - - session.invalidate(CLIENT_BEFORE_REMOVE, realm, client); - - storeWithRealm(realm).delete(id); - - session.invalidate(CLIENT_AFTER_REMOVE, client); - - return true; - } - - @Override - public long getClientsCount(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return storeWithRealm(realm).getCount(withCriteria(mcb)); - } - - @Override - public ClientModel getClientById(RealmModel realm, String id) { - if (id == null) { - return null; - } - - LOG.tracef("getClientById(%s, %s)%s", realm, id, getShortStackTrace()); - - MapClientEntity entity = storeWithRealm(realm).read(id); - return (entity == null || ! entityRealmFilter(realm).test(entity)) - ? null - : entityToAdapterFunc(realm).apply(entity); - } - - @Override - public ClientModel getClientByClientId(RealmModel realm, String clientId) { - if (clientId == null) { - return null; - } - LOG.tracef("getClientByClientId(%s, %s)%s", realm, clientId, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CLIENT_ID, Operator.EQ, clientId); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(entityToAdapterFunc(realm)) - .findFirst() - .orElse(null) - ; - } - - @Override - public Stream searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { - if (clientId == null) { - return Stream.empty(); - } - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CLIENT_ID, Operator.ILIKE, "%" + clientId + "%"); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.CLIENT_ID)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream searchClientsByAttributes(RealmModel realm, Map attributes, Integer firstResult, Integer maxResults) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - for (Map.Entry entry : attributes.entrySet()) { - mcb = mcb.compare(SearchableFields.ATTRIBUTE, Operator.EQ, entry.getKey(), entry.getValue()); - } - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.CLIENT_ID)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public void addClientScopes(RealmModel realm, ClientModel client, Set clientScopes, boolean defaultScope) { - final String id = client.getId(); - MapClientEntity entity = storeWithRealm(realm).read(id); - - if (entity == null) return; - - // Defaults to openid-connect - String clientProtocol = client.getProtocol() == null ? "openid-connect" : client.getProtocol(); - - LOG.tracef("addClientScopes(%s, %s, %s, %b)%s", realm, client, clientScopes, defaultScope, getShortStackTrace()); - - Map existingClientScopes = getClientScopes(realm, client, true); - existingClientScopes.putAll(getClientScopes(realm, client, false)); - - clientScopes.stream() - .filter(clientScope -> ! existingClientScopes.containsKey(clientScope.getName())) - .filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol)) - .forEach(clientScope -> entity.setClientScope(clientScope.getId(), defaultScope)); - } - - @Override - public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) { - final String id = client.getId(); - MapClientEntity entity = storeWithRealm(realm).read(id); - - if (entity == null) return; - - LOG.tracef("removeClientScope(%s, %s, %s)%s", realm, client, clientScope, getShortStackTrace()); - - entity.removeClientScope(clientScope.getId()); - } - - @Override - public Map getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) { - final String id = client.getId(); - MapClientEntity entity = storeWithRealm(realm).read(id); - - if (entity == null) return null; - - // Defaults to openid-connect - String clientProtocol = client.getProtocol() == null ? "openid-connect" : client.getProtocol(); - - LOG.tracef("getClientScopes(%s, %s, %b)%s", realm, client, defaultScopes, getShortStackTrace()); - - return entity.getClientScopes(defaultScopes) - .map(clientScopeId -> session.clientScopes().getClientScopeById(realm, clientScopeId)) - .filter(Objects::nonNull) - .filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol)) - .collect(Collectors.toMap(ClientScopeModel::getName, Function.identity())); - } - - @Override - public Map> getAllRedirectUrisOfEnabledClients(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ENABLED, Operator.EQ, Boolean.TRUE); - - try (Stream st = storeWithRealm(realm).read(withCriteria(mcb))) { - return st - .filter(mce -> mce.getRedirectUris() != null && ! mce.getRedirectUris().isEmpty()) - .collect(Collectors.toMap( - mce -> entityToAdapterFunc(realm).apply(mce), - mce -> new HashSet<>(mce.getRedirectUris())) - ); - } - } - - public void preRemove(RealmModel realm, RoleModel role) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.SCOPE_MAPPING_ROLE, Operator.EQ, role.getId()); - - try (Stream toRemove = storeWithRealm(realm).read(withCriteria(mcb))) { - toRemove - .forEach(clientEntity -> clientEntity.removeScopeMapping(role.getId())); - } - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void close() { - - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java deleted file mode 100644 index 82df00ad311..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java +++ /dev/null @@ -1,68 +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 java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import org.keycloak.models.ClientModel; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.ClientProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE; - -/** - * - * @author hmlnarik - */ -public class MapClientProviderFactory extends AbstractMapProviderFactory implements ClientProviderFactory, InvalidationHandler { - - private final ConcurrentHashMap> REGISTERED_NODES_STORE = new ConcurrentHashMap<>(); - - public MapClientProviderFactory() { - super(ClientModel.class, MapClientProvider.class); - } - - @Override - public MapClientProvider createNew(KeycloakSession session) { - return new MapClientProvider(session, getMapStorage(session), REGISTERED_NODES_STORE); - } - - @Override - public String getHelpText() { - return "Client provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0]); - } else if (type == ROLE_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]); - } else if (type == CLIENT_AFTER_REMOVE) { - session.getKeycloakSessionFactory().publish(new ClientModel.ClientRemovedEvent() { - @Override public ClientModel getClient() { return (ClientModel) params[0]; } - @Override public KeycloakSession getKeycloakSession() { return session; } - }); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapProtocolMapperEntity.java b/model/map/src/main/java/org/keycloak/models/map/client/MapProtocolMapperEntity.java deleted file mode 100644 index 25294a0158c..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapProtocolMapperEntity.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.client; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import java.util.Map; - -/** - * - * @author hmlnarik - */ -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapProtocolMapperEntity extends UpdatableEntity, AbstractEntity { - - String getName(); - void setName(String name); - - String getProtocolMapper(); - void setProtocolMapper(String protocolMapper); - - Map getConfig(); - void setConfig(Map config); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapProtocolMapperUtils.java b/model/map/src/main/java/org/keycloak/models/map/client/MapProtocolMapperUtils.java deleted file mode 100644 index 03305c01bfe..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapProtocolMapperUtils.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 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 java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.map.common.DeepCloner; - -public class MapProtocolMapperUtils { - - private final String protocol; - private static final ConcurrentMap INSTANCES = new ConcurrentHashMap<>(); - - private MapProtocolMapperUtils(String protocol) { - this.protocol = protocol; - } - - public static MapProtocolMapperUtils instanceFor(String protocol) { - Objects.requireNonNull(protocol); - return INSTANCES.computeIfAbsent(protocol, MapProtocolMapperUtils::new); - } - - public static MapProtocolMapperEntity fromModel(ProtocolMapperModel model) { - MapProtocolMapperEntity res = DeepCloner.DUMB_CLONER.newInstance(MapProtocolMapperEntity.class); - res.setId(model.getId()); - res.setName(model.getName()); - res.setProtocolMapper(model.getProtocolMapper()); - res.setConfig(model.getConfig()); - return res; - } - - public ProtocolMapperModel toModel(MapProtocolMapperEntity entity) { - ProtocolMapperModel res = new ProtocolMapperModel(); - res.setId(entity.getId()); - res.setName(entity.getName()); - res.setProtocolMapper(entity.getProtocolMapper()); - Map config = entity.getConfig(); - res.setConfig(config == null ? new HashMap<>(): new HashMap<>(config)); - res.setProtocol(protocol); - return res; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/AbstractClientScopeModel.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/AbstractClientScopeModel.java deleted file mode 100644 index 5e6fcb6df7a..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/AbstractClientScopeModel.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.clientscope; - -import java.util.Objects; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractEntity; - -public abstract class AbstractClientScopeModel implements ClientScopeModel { - - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractClientScopeModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ClientScopeModel)) return false; - - ClientScopeModel that = (ClientScopeModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java deleted file mode 100644 index afe84db7772..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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.clientscope; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.jboss.logging.Logger; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.client.MapProtocolMapperUtils; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.RoleUtils; - -public class MapClientScopeAdapter extends AbstractClientScopeModel implements ClientScopeModel { - - private static final Logger LOG = Logger.getLogger(MapClientScopeAdapter.class); - private final MapProtocolMapperUtils pmUtils; - - public MapClientScopeAdapter(KeycloakSession session, RealmModel realm, MapClientScopeEntity entity) { - super(session, realm, entity); - pmUtils = MapProtocolMapperUtils.instanceFor(safeGetProtocol()); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - entity.setName(KeycloakModelUtils.convertClientScopeName(name)); - } - - @Override - public String getDescription() { - return entity.getDescription(); - } - - @Override - public void setDescription(String description) { - entity.setDescription(description); - } - - @Override - public String getProtocol() { - return entity.getProtocol(); - } - - @Override - public void setProtocol(String protocol) { - entity.setProtocol(protocol); - } - - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public void setAttribute(String name, String value) { -// TODO: https://github.com/keycloak/keycloak/issues/9741 -// boolean valueUndefined = value == null || "".equals(value.trim()); -// -// if (valueUndefined) { -// removeAttribute(name); -// return; -// } - - entity.setAttribute(name, Collections.singletonList(value)); - } - - @Override - public void removeAttribute(String name) { - entity.removeAttribute(name); - } - - @Override - public String getAttribute(String name) { - List attribute = entity.getAttribute(name); - if (attribute == null || attribute.isEmpty()) return null; - return attribute.get(0); - } - - @Override - public Map getAttributes() { - 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; - } else if (entry.getValue().size() > 1) { - // This could be caused by an inconsistency in the storage, a programming error, - // or a downgrade from a future version of Keycloak that already supports multi-valued attributes. - // The caller will not see the other values, and when this entity is later updated, the additional values will be lost. - LOG.warnf("ClientScope '%s' realm '%s' has attribute '%s' with %d values, retrieving only the first", getName(), getRealm().getName(), entry.getKey(), - entry.getValue().size()); - } - return entry.getValue().get(0); - }) - ); - } - - /*************** Protocol mappers ****************/ - - private String safeGetProtocol() { - return entity.getProtocol() == null ? "openid-connect" : entity.getProtocol(); - } - - @Override - public Stream getProtocolMappersStream() { - final Set protocolMappers = entity.getProtocolMappers(); - return protocolMappers == null ? Stream.empty() : protocolMappers.stream().distinct().map(pmUtils::toModel); - } - - @Override - public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) { - if (model == null) { - return null; - } - - MapProtocolMapperEntity pm = MapProtocolMapperUtils.fromModel(model); - if (pm.getId() == null) { - String id = KeycloakModelUtils.generateId(); - pm.setId(id); - } - if (model.getConfig() == null) { - pm.setConfig(new HashMap<>()); - } - - entity.addProtocolMapper(pm); - return pmUtils.toModel(pm); - } - - @Override - public void removeProtocolMapper(ProtocolMapperModel mapping) { - final String id = mapping == null ? null : mapping.getId(); - if (id != null) { - entity.removeProtocolMapper(id); - } - } - - @Override - public void updateProtocolMapper(ProtocolMapperModel mapping) { - final String id = mapping == null ? null : mapping.getId(); - if (id != null) { - entity.getProtocolMapper(id).ifPresent((pmEntity) -> { - entity.removeProtocolMapper(id); - addProtocolMapper(mapping); - }); - } - } - - @Override - public ProtocolMapperModel getProtocolMapperById(String id) { - return entity.getProtocolMapper(id).map(pmUtils::toModel).orElse(null); - } - - @Override - public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) { - final Set protocolMappers = entity.getProtocolMappers(); - if (! Objects.equals(protocol, safeGetProtocol())) { - return null; - } - return protocolMappers == null ? null : protocolMappers.stream() - .filter(pm -> Objects.equals(pm.getName(), name)) - .map(pmUtils::toModel) - .findAny() - .orElse(null); - } - - /*************** Scopes mappings ****************/ - - @Override - public Stream getScopeMappingsStream() { - final Collection scopeMappings = this.entity.getScopeMappings(); - return scopeMappings == null ? Stream.empty() : scopeMappings.stream() - .map(realm::getRoleById) - .filter(Objects::nonNull); - } - - @Override - public Stream getRealmScopeMappingsStream() { - return getScopeMappingsStream().filter(r -> RoleUtils.isRealmRole(r, realm)); - } - - @Override - public void addScopeMapping(RoleModel role) { - final String id = role == null ? null : role.getId(); - if (id != null) { - this.entity.addScopeMapping(id); - } - } - - @Override - public void deleteScopeMapping(RoleModel role) { - final String id = role == null ? null : role.getId(); - if (id != null) { - this.entity.removeScopeMapping(id); - } - } - - @Override - public boolean hasScope(RoleModel role) { - return RoleUtils.hasRole(getScopeMappingsStream(), role); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), System.identityHashCode(this)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java deleted file mode 100644 index cffd48eada8..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.clientscope; - -import java.util.Collection; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.clientscope.MapClientScopeEntity.AbstractClientScopeEntity" -) -@DeepCloner.Root -public interface MapClientScopeEntity extends AbstractEntity, UpdatableEntity, EntityWithAttributes { - - public abstract class AbstractClientScopeEntity extends UpdatableEntity.Impl implements MapClientScopeEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getName(); - - String getDescription(); - - String getProtocol(); - - String getRealmId(); - - void setName(String name); - - void setDescription(String description); - - void setProtocol(String protocol); - - void setRealmId(String realmId); - - Optional getProtocolMapper(String id); - Set getProtocolMappers(); - void addProtocolMapper(MapProtocolMapperEntity mapping); - void removeProtocolMapper(String id); - - void addScopeMapping(String id); - void removeScopeMapping(String id); - Collection getScopeMappings(); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java deleted file mode 100644 index 135cd6aab0d..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.clientscope; - -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.jboss.logging.Logger; -import org.keycloak.models.ClientScopeModel.SearchableFields; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.ClientScopeProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.KeycloakModelUtils; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapClientScopeProvider implements ClientScopeProvider { - - private static final Logger LOG = Logger.getLogger(MapClientScopeProvider.class); - private final KeycloakSession session; - private final MapStorage store; - private final boolean storeHasRealmId; - - public MapClientScopeProvider(KeycloakSession session, MapStorage clientScopeStore) { - this.session = session; - this.store = clientScopeStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - - return origEntity -> new MapClientScopeAdapter(session, realm, origEntity); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private Predicate entityRealmFilter(RealmModel realm) { - if (realm == null || realm.getId() == null) { - return c -> false; - } - String realmId = realm.getId(); - return entity -> Objects.equals(realmId, entity.getRealmId()); - } - - @Override - public Stream getClientScopesStream(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public ClientScopeModel addClientScope(RealmModel realm, String id, String name) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.NAME, Operator.EQ, name); - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("Client scope with name '" + name + "' in realm " + realm.getName()); - } - - if (id != null && storeWithRealm(realm).exists(id)) { - throw new ModelDuplicateException("Client scope exists: " + id); - } - - LOG.tracef("addClientScope(%s, %s, %s)%s", realm, id, name, getShortStackTrace()); - - MapClientScopeEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapClientScopeEntity.class); - entity.setId(id); - entity.setRealmId(realm.getId()); - entity.setName(KeycloakModelUtils.convertClientScopeName(name)); - - entity = storeWithRealm(realm).create(entity); - return entityToAdapterFunc(realm).apply(entity); - } - - @Override - public boolean removeClientScope(RealmModel realm, String id) { - if (id == null) return false; - ClientScopeModel clientScope = getClientScopeById(realm, id); - if (clientScope == null) return false; - - session.invalidate(CLIENT_SCOPE_BEFORE_REMOVE, realm, clientScope); - - storeWithRealm(realm).delete(id); - - session.invalidate(CLIENT_SCOPE_AFTER_REMOVE, clientScope); - - return true; - } - - @Override - public void removeClientScopes(RealmModel realm) { - LOG.tracef("removeClients(%s)%s", realm, getShortStackTrace()); - - getClientScopesStream(realm) - .map(ClientScopeModel::getId) - .collect(Collectors.toSet()) // This is necessary to read out all the client IDs before removing the clients - .forEach(id -> removeClientScope(realm, id)); - } - - @Override - public ClientScopeModel getClientScopeById(RealmModel realm, String id) { - if (id == null) { - return null; - } - - LOG.tracef("getClientScopeById(%s, %s)%s", realm, id, getShortStackTrace()); - - MapClientScopeEntity entity = storeWithRealm(realm).read(id); - return (entity == null || ! entityRealmFilter(realm).test(entity)) - ? null - : entityToAdapterFunc(realm).apply(entity); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void close() { - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProviderFactory.java deleted file mode 100644 index 10b3abd1d6c..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProviderFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.clientscope; - -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.ClientScopeProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; - -public class MapClientScopeProviderFactory extends AbstractMapProviderFactory implements ClientScopeProviderFactory, InvalidationHandler { - - public MapClientScopeProviderFactory() { - super(ClientScopeModel.class, MapClientScopeProvider.class); - } - - @Override - public MapClientScopeProvider createNew(KeycloakSession session) { - return new MapClientScopeProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "Client scope provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0]); - } else if (type == CLIENT_SCOPE_BEFORE_REMOVE) { - ((RealmModel) params[0]).removeDefaultClientScope((ClientScopeModel) params[1]); - } else if (type == CLIENT_SCOPE_AFTER_REMOVE) { - session.getKeycloakSessionFactory().publish(new ClientScopeModel.ClientScopeRemovedEvent() { - @Override public ClientScopeModel getClientScope() { return (ClientScopeModel) params[0]; } - @Override public KeycloakSession getKeycloakSession() { return session; } - }); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/AbstractEntity.java b/model/map/src/main/java/org/keycloak/models/map/common/AbstractEntity.java deleted file mode 100644 index cda50664a29..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/AbstractEntity.java +++ /dev/null @@ -1,30 +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.common; - -import org.keycloak.models.map.annotations.CollectionKey; - -/** - * - * @author hmlnarik - */ -public interface AbstractEntity { - - @CollectionKey - String getId(); - void setId(String id); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java deleted file mode 100644 index e5aad9e7b21..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java +++ /dev/null @@ -1,150 +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.common; - -import java.util.concurrent.atomic.AtomicInteger; -import org.keycloak.Config.Scope; -import org.keycloak.common.Profile; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelException; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.InvalidationHandler; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import java.util.Objects; -import org.jboss.logging.Logger; - -import static org.keycloak.models.map.common.SessionAttributesUtils.grabNewFactoryIdentifier; - -/** - * - * @author hmlnarik - */ -public abstract class AbstractMapProviderFactory implements AmphibianProviderFactory, EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "map"; - - public static final String CONFIG_STORAGE = "storage"; - - protected final Logger LOG = Logger.getLogger(getClass()); - - public static final AtomicInteger uniqueCounter = new AtomicInteger(); - private final int factoryId = grabNewFactoryIdentifier(); - - protected final Class modelType; - private final Class providerType; - - private Scope storageConfigScope; - - protected AbstractMapProviderFactory(Class modelType, Class providerType) { - this.modelType = modelType; - this.providerType = providerType; - } - - public enum MapProviderObjectType implements InvalidationHandler.InvalidableObjectType { - CLIENT_BEFORE_REMOVE, - CLIENT_AFTER_REMOVE, - CLIENT_SCOPE_BEFORE_REMOVE, - CLIENT_SCOPE_AFTER_REMOVE, - GROUP_BEFORE_REMOVE, - GROUP_AFTER_REMOVE, - REALM_BEFORE_REMOVE, - REALM_AFTER_REMOVE, - RESOURCE_SERVER_BEFORE_REMOVE, - RESOURCE_SERVER_AFTER_REMOVE, - ROLE_BEFORE_REMOVE, - ROLE_AFTER_REMOVE, - USER_BEFORE_REMOVE, - USER_AFTER_REMOVE - } - - /** - * Creates new instance of a provider. - * - * @param session - * @return See description. - */ - public abstract T createNew(KeycloakSession session); - - /** - * Returns instance of a provider. If the instance is already created within - * the session (it's found in session attributes), it's returned from there, - * otherwise new instance is created (and stored among the session attributes). - * - * @param session - * @return See description. - */ - @Override - public T create(KeycloakSession session) { - return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, providerType, this::createNew); - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - public MapStorage getMapStorage(KeycloakSession session) { - ProviderFactory storageProviderFactory = getProviderFactoryOrComponentFactory(session, storageConfigScope); - final MapStorageProvider factory = storageProviderFactory.create(session); - session.enlistForClose(factory); - return factory.getMapStorage(modelType); - } - - public static ProviderFactory getProviderFactoryOrComponentFactory(KeycloakSession session, Scope storageConfigScope) { - ProviderFactory storageProviderFactory; - if (!hasRealmSpecificStorage(session, storageConfigScope)) { - String provider = storageConfigScope.get("provider"); - if (provider == null) { - storageProviderFactory = session.getKeycloakSessionFactory().getProviderFactory(MapStorageProvider.class); - } else { - storageProviderFactory = session.getKeycloakSessionFactory().getProviderFactory(MapStorageProvider.class, provider); - Objects.requireNonNull(storageProviderFactory, "Could not find map storage provider " + provider); - } - } else { - // If this is being implemented, make sure that the factory is being closed eventually. - // When no cluster provider is available, the componentFactory will not be cached and a new instance is being returned all the time - // when calling `getComponentFactory(session.getKeycloakSessionFactory(), MapStorageProvider.class, storageConfigScope, MapStorageSpi.NAME)`. - throw new ModelException("not supported yet"); - } - return storageProviderFactory; - } - - private static boolean hasRealmSpecificStorage(KeycloakSession session, Scope storageConfigScope) { - // Once there is functionality for a realm-specific storage, implement the logic on how to detect it here. - return false; - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void init(Scope config) { - // Implementation of the map storage SPI - this.storageConfigScope = config.scope(CONFIG_STORAGE); - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/CastUtils.java b/model/map/src/main/java/org/keycloak/models/map/common/CastUtils.java deleted file mode 100644 index 511ba9126b2..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/CastUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2023 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.common; - -import java.util.Objects; -import java.util.function.Function; - -/** - * This class contains utility classes for type conversion. - * - * @author hmlnarik - */ -public class CastUtils { - - /** - * Converts value to destination class (if it can). - * @param value Value to convert - * @param toClass Class to convert value to - * @return Value converted to the given class - * @throws IllegalStateException if the value cannot be converted to the requested class - */ - @SuppressWarnings("unchecked") - public static T cast(Object value, Class toClass) { - return value == null ? null : ((Function) getCastFunc(value.getClass(), toClass)).apply(value); - } - - /** - * Provides a function to convert value of a given class to destination class (if it can). - * @param fromClass Class to convert value from - * @param toClass Class to convert value to - * @return Function {@code fromClass -> toClass} converting values from the {@code fromClass} to the {@code toClass} - * @throws IllegalStateException if the value cannot be converted to the requested class - */ - public static > Function getCastFunc(Class fromClass, Class toClass) { - if (fromClass == toClass || toClass.isAssignableFrom(fromClass)) { - return Function.identity(); - } - if (toClass == String.class) { - return Objects::toString; - } - if (fromClass == String.class) { - if (toClass == Integer.class) { - return (Function) Integer::valueOf; - } else if (toClass == Long.class) { - return (Function) Long::valueOf; - } else if (toClass == Boolean.class) { - return (Function) Boolean::valueOf; - } else if (toClass.isEnum()) { - @SuppressWarnings("unchecked") - Class enumClass = (Class) toClass; - return (String value) -> Enum.valueOf(enumClass, value); - } - } - if (fromClass == Long.class) { - if (toClass == Integer.class) { - return (Function) Long::intValue; - } - } - if (fromClass == Integer.class) { - if (toClass == Long.class) { - return (Function) Integer::longValue; - } - } - - throw new IllegalStateException("Unknown cast: " + fromClass + " -> " + toClass); - } - -} - diff --git a/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java b/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java deleted file mode 100644 index 5741918e1dd..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/DeepCloner.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * 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.common; - -import org.keycloak.models.map.common.delegate.DelegateProvider; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Stack; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import org.jboss.logging.Logger; - -/** - * Helper class for deep cloning and fine-grained instantiation per interface and deep copying their properties. - *

- * This class is intended to be used by individual map storage implementations for copying - * over entities into their native implementations. - *

- * For example, a {@code MapClientEntity} interface could be implemented by {@code MapClientEntityImpl} - * (used by a file-based storage in this example) and an {@code HotRodClientEntityImpl} (for Infinispan). - * Say that the Infinispan is stacked on top of the file-based storage to provide caching layer. - * Upon first read, a {@code MapClientEntityImpl} could be obtained from file-based storage and passed - * to Infinispan layer for caching. Infinispan, regardless of the actual implementation, need to store - * the {@code MapClientEntity} data in a form that can be processed and sent over the wire in Infinispan - * (say in an {@code InfinispanClientEntityImpl}). To achieve this, the Infinispan store has to clone - * the file entity values from the {@code MapClientEntityImpl} to {@code InfinispanClientEntityImpl}, - * i.e. it performs deep cloning, using this helper class. - *

- * Broader context: - * In tree store, map storages are agnostic to their neighbours. Therefore each implementation can be - * provided with a record (a {@code MapClientEntity} instance in the example above) originating from - * any other implementation. For a map storage to process the record (beyond read-only mode), - * it needs to be able to clone it into its own entity. Each of the storages thus can benefit from - * the {@code DeepCloner} capabilities. - * - * @author hmlnarik - */ -public class DeepCloner { - - /** - * Marker for interfaces that could be requested for instantiation and cloning. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface Root {} - - /** - * Function that clones properties from {@code original} object to a {@code target} object and returns - * the cloned object (usually the same as the {@code target}). - * @param Object class - */ - @FunctionalInterface - public interface Cloner { - /** - * Function that clones properties from {@code original} object to a {@code target} object and returns - * the cloned object (usually the same as the {@code target}). - */ - V clone(V original, V target); - } - - /** - * Function that instantiates a delegation object of type {@code V} with the given delegate provider - * @param Object class - */ - @FunctionalInterface - public interface DelegateCreator { - /** - * Function that instantiates a delegation object of type {@code V} with the given delegate provider. - */ - V create(DelegateProvider delegateProvider); - } - - /** - * Function that instantiates a delegation object of type {@code V} with the given per-field delegate provider - * @param Object class - */ - @FunctionalInterface - public interface EntityFieldDelegateCreator { - /** - * Function that instantiates a delegation object of type {@code V} with the given per-field delegate provider. - */ - V create(EntityFieldDelegate entityDelegateProvider); - } - - public static final DeepCloner DUMB_CLONER = new Builder().build(); - - /** - * Builder for the {@code DeepCloner} helper class. - */ - public static class Builder { - private final Map, Function> constructors = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.CONSTRUCTORS_DC); - private final Map, Cloner> clonersWithId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.CLONERS_WITH_ID); - private final Map, Cloner> clonersWithoutId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.CLONERS_WITHOUT_ID); - private final Map, DelegateCreator> delegateCreators = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.DELEGATE_CREATORS); - private final Map, EntityFieldDelegateCreator> entityFieldDelegateCreators = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.ENTITY_FIELD_DELEGATE_CREATORS); - private Cloner genericCloner = (from, to) -> { throw new IllegalStateException("Cloner not found for class " + (from == null ? "" : from.getClass())); }; - - /** - * Returns a {@link DeepCloner} initialized with the respective constructors and cloners. - * @return - */ - public DeepCloner build() { - return new DeepCloner(constructors, delegateCreators, entityFieldDelegateCreators, clonersWithId, clonersWithoutId, genericCloner); - } - - private void forThisClassAndAllMarkedParentsAndInterfaces(Class rootClazz, Consumer> action) { - action.accept(rootClazz); - - Stack> c = new Stack<>(); - c.push(rootClazz); - while (! c.isEmpty()) { - Class cl = c.pop(); - if (cl == null) { - continue; - } - - c.push(cl.getSuperclass()); - for (Class iface : cl.getInterfaces()) { - c.push(iface); - } - - if (cl.getAnnotation(Root.class) != null) { - action.accept(cl); - } - } - } - - /** - * Adds a method, often a constructor, that instantiates a record of type {@code V}. - * - * @param Class or interface that would be instantiated by the given methods - * @param clazz Class or interface that would be instantiated by the given methods - * @param constructor Function that creates a new instance of class {@code V}. - * If {@code null}, such a single-parameter constructor is not available. - * @return This builder. - */ - public Builder constructor(Class clazz, Function constructor) { - if (constructor != null) { - forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.constructors.put(cl, constructor)); - } - return this; - } - - /** - * Adds a method that instantiates an per-field delegate of type {@code V}. - * - * @param Class or interface that would be instantiated by the given methods - * @param clazz Class or interface that would be instantiated by the given methods - * @param delegateCreator Function that creates a new instance of class {@code V}. - * If {@code null}, such a single-parameter constructor is not available. - * @return This builder. - */ - public Builder delegateCreator(Class clazz, EntityFieldDelegateCreator delegateCreator) { - if (delegateCreator != null) { - forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.entityFieldDelegateCreators.put(cl, delegateCreator)); - } - return this; - } - - /** - * Adds a method, often a constructor, that instantiates a delegate of type {@code V}. - * - * @param Class or interface that would be instantiated by the given methods - * @param clazz Class or interface that would be instantiated by the given methods - * @param delegateCreator Function that creates a new instance of class {@code V}. - * If {@code null}, such a single-parameter constructor is not available. - * @return This builder. - */ - public Builder delegateCreator(Class clazz, DelegateCreator delegateCreator) { - if (delegateCreator != null) { - forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.delegateCreators.put(cl, delegateCreator)); - } - return this; - } - - /** - * Adds a method that copies (as in a deep copy) an object properties from one object to another - * - * @param Class or interface whose instance would be copied over to another instance by the given cloner - * @param clazz Class or interface whose instance would be copied over to another instance by the given cloner - * @param cloner A method for cloning with the following signature: {@code V deepClone(V from, V to)} which - * copies properties of an object {@code from} onto the object {@code to}. This - * function usually returns {@code to} - * @return This builder. - */ - public Builder cloner(Class clazz, Cloner cloner) { - if (cloner != null) { - forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, cloner)); - } - return this; - } - - /** - * Adds a method that copies (as in a deep copy) an object properties from one object to another - * - * @param Class or interface whose instance would be copied over to another instance by the given cloner - * @param clazz Class or interface whose instance would be copied over to another instance by the given cloner - * @param clonerWithId A method for cloning with the following signature: {@code V deepClone(V from, V to)} which - * copies properties of an object {@code from} onto the object {@code to}. This - * function usually returns {@code to} - * @return This builder. - */ - public Builder cloner(Class clazz, Cloner clonerWithId, Cloner clonerWithoutId) { - if (clonerWithId != null) { - forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, clonerWithId)); - } - if (clonerWithoutId != null) { - forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithoutId.put(cl, clonerWithoutId)); - } - return this; - } - - /** - * Adds a method that copies (as in a deep copy) an object properties to another object for any class - * that is not covered by a specific cloner set via {@link #cloner(Class, BiFunction)} method. - * - * @param Class or interface whose instance would be copied over to another instance by the given cloner - * @param genericCloner A method for cloning which copies properties of an object onto another object. This - * function usually returns {@code to} - * @return This builder. - */ - public Builder genericCloner(Cloner genericCloner) { - this.genericCloner = genericCloner; - return this; - } - } - - private static final Logger LOG = Logger.getLogger(DeepCloner.class); - - private final Map, Function> constructors; - private final Map, Cloner> clonersWithId; - private final Map, Cloner> clonersWithoutId; - private final Map, DelegateCreator> delegateCreators; - private final Map, EntityFieldDelegateCreator> entityFieldDelegateCreators; - private final Cloner genericCloner; - private final Map, Object> emptyInstances = new HashMap<>(AutogeneratedClasses.EMPTY_INSTANCES); - - private DeepCloner(Map, Function> constructors, - Map, DelegateCreator> delegateCreators, - Map, EntityFieldDelegateCreator> entityFieldDelegateCreators, - Map, Cloner> clonersWithId, - Map, Cloner> clonersWithoutId, - Cloner genericCloner) { - this.constructors = constructors; - this.clonersWithId = clonersWithId; - this.clonersWithoutId = clonersWithoutId; - this.delegateCreators = delegateCreators; - this.genericCloner = genericCloner; - this.entityFieldDelegateCreators = entityFieldDelegateCreators; - } - - private V getFromClassRespectingHierarchy(Map, V> map, Class clazz) { - // fast lookup - V res = map.get(clazz); - if (res != null) { - return res; - } - - // BFS on implemented supertypes and interfaces. Skip clazz as it has been looked up already - LinkedList> ll = new LinkedList<>(); - ll.push(clazz.getSuperclass()); - for (Class iface : clazz.getInterfaces()) { - ll.push(iface); - } - - while (! ll.isEmpty()) { - Class cl = ll.pollFirst(); - if (cl == null) { - continue; - } - - res = map.get(cl); - if (res != null) { - map.put(clazz, res); // Wire clazz with the result for fast lookup next time - return res; - } - - ll.push(cl.getSuperclass()); - ll.addAll(Arrays.asList(cl.getInterfaces())); - } - return null; - } - - @SuppressWarnings("unchecked") - public D delegate(V delegate, DelegateProvider delegateProvider) { - return delegate((Class) delegate.getClass(), delegateProvider); - } - - public D delegate(Class delegateClass, DelegateProvider delegateProvider) { - @SuppressWarnings("unchecked") - DelegateCreator delegateCreator = (DelegateCreator) getFromClassRespectingHierarchy(delegateCreators, delegateClass); - if (delegateCreator != null) { - return delegateCreator.create(delegateProvider); - } - throw new IllegalStateException("Cannot create delegate for " + delegateClass); - } - - @SuppressWarnings("unchecked") - public V entityFieldDelegate(V delegate, EntityFieldDelegate delegateProvider) { - return entityFieldDelegate((Class) delegate.getClass(), delegateProvider); - } - - public V entityFieldDelegate(Class delegateClass, EntityFieldDelegate delegateProvider) { - @SuppressWarnings("unchecked") - EntityFieldDelegateCreator delegateCreator = (EntityFieldDelegateCreator) getFromClassRespectingHierarchy(entityFieldDelegateCreators, delegateClass); - if (delegateCreator != null) { - return delegateCreator.create(delegateProvider); - } - throw new IllegalStateException("Cannot create delegate for " + delegateClass); - } - - public V emptyInstance(Class instanceClass) { - @SuppressWarnings("unchecked") - V emptyInstance = (V) getFromClassRespectingHierarchy(emptyInstances, instanceClass); - if (emptyInstance != null) { - return emptyInstance; - } - throw new IllegalStateException("Cannot create empty instance for " + instanceClass); - } - - /** - * Creates a new instance of the given class or interface if the parameterless constructor for that type is known. - * @param Type (class or a {@code @Root} interface) to create a new instance - * @param clazz Type (class or a {@code @Root} interface) to create a new instance - * @return A new instance - * @throws IllegalStateException When the constructor is not known. - */ - public V newInstance(Class clazz) { - if (clazz == null) { - return null; - } - - V res; - @SuppressWarnings("unchecked") - Function c = (Function) getFromClassRespectingHierarchy(this.constructors, clazz); - if (c == null) { - try { - res = clazz.getDeclaredConstructor().newInstance(); - } catch (ReflectiveOperationException ex) { - res = null; - } - } else { - res = c.apply(this); - } - - if (res == null) { - throw new IllegalStateException("Cannot instantiate " + clazz); - } - - return res; - } - - /** - * Returns a class type of an instance that would be instantiated by {@link #newInstance(java.lang.Class)} method. - * @param Type (class or a {@code @Root} interface) to create a new instance - * @param valueType Type (class or a {@code @Root} interface) to create a new instance - * @return See description - */ - @SuppressWarnings("unchecked") - public Class newInstanceType(Class valueType) { - if (valueType == null) { - return null; - } - try { - V v = newInstance(valueType); - return v == null ? null : (Class) v.getClass(); - } catch (IllegalStateException ex) { - return null; - } - } - - /** - * Deeply clones properties from the {@code from} instance to the {@code to} instance. - * @param Type (class or a {@code @Root} interface) to clone the instance - * @param from Original instance - * @param to Instance to copy the properties onto - * @return Instance which has all the properties same as the {@code from}. Preferably, {@code to} is returned. - * However {@code from} is returned if the cloner is not known and generic cloner is not available. - */ - public V deepClone(V from, V to) { - return deepClone(from, to, this.clonersWithId); - } - - /** - * Deeply clones properties from the {@code from} instance to the {@code to} instance excluding the ID field. - * @param Type (class or a {@code @Root} interface) to clone the instance - * @param from Original instance - * @param to Instance to copy the properties onto - * @return Instance which has all the properties same as the {@code from}. Preferably, {@code to} is returned. - * However {@code from} is returned if the cloner is not known and generic cloner is not available. - */ - public V deepCloneNoId(V from, V to) { - return deepClone(from, to, this.clonersWithoutId); - } - - @SuppressWarnings("unchecked") - private V deepClone(V from, V to, Map, Cloner> cloners) { - Cloner cloner = (Cloner) getFromClassRespectingHierarchy(cloners, from.getClass()); - if (cloner != null) { - return cloner.clone(from, to); - } - - if (genericCloner != null) { - LOG.debugf("Using generic cloner for %s", from.getClass()); - final V res = ((Cloner) genericCloner).clone(from, to); - - if (res instanceof UpdatableEntity) { - ((UpdatableEntity) res).clearUpdatedFlag(); - } - - return res; - } - - return warnCloneNotSupported(from); - } - - /** - * Creates a new instance of the given type and copies its properties from the {@code from} instance - * @param Type (class or a {@code @Root} interface) to create a new instance and clone properties from - * @param newId ID of the new object - * @param from Original instance - * @return Newly created instance or {@code null} if {@code from} is {@code null}. - */ - @SuppressWarnings("unchecked") - public V from(String newId, V from) { - if (from == null) { - return null; - } - final V res = newInstance((Class) from.getClass()); - if (newId != null) { - res.setId(newId); - } - return deepCloneNoId(from, res); - } - - /** - * Creates a new instance of the given type and copies its properties from the {@code from} instance - * @param Type (class or a {@code @Root} interface) to create a new instance and clone properties from - * @param from Original instance - * @return Newly created instance or {@code null} if {@code from} is {@code null}. - */ - @SuppressWarnings("unchecked") - public V from(V from) { - return from == null ? null : deepClone(from, newInstance((Class) from.getClass())); - } - - /** - * Issues warning in the logs and returns the input parameter {@code o} - * @param o - * @return The {@code o} object - */ - public static T warnCloneNotSupported(T o) { - if (o != null) { - LOG.warnf("Cloning not supported for %s, returning the same instance!", o.getClass()); - } - return o; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/EntityField.java b/model/map/src/main/java/org/keycloak/models/map/common/EntityField.java deleted file mode 100644 index b7ef7dba374..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/EntityField.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.keycloak.models.map.common; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Represents a field in an entity with appropriate accessors. - * - * @author hmlnarik - * @param - */ -public interface EntityField { - - /** - * Returns name of this field with no spaces where each word starts with a capital letter. - * @return - */ - String getName(); - /* - * Returns name of this field in camel case with first word starting with lower case letter and - * further words starting with a capital letter. - * @return - */ - String getNameCamelCase(); - /** - * Returns name of this field in lowercase with words separated by a dash ({@code -}). - * @return - */ - String getNameDashed(); - /** - * Returns the value of this field. - * - * @param e Entity - * @return Value of the field - */ - Object get(E e); - - /** - * Sets the value of this field. Does nothing by default. If you want to have a field set, override this method. - * @param - * @param e Entity - * @param value Value of the field - */ - default void set(E e, T value) {}; - - /** - * Adds an element to the collection stored in this field. - * @param e Entity - * @param value Value to be added to the collection - * @throws ClassCastException If this field is not a collection. - * @throws UnsupportedOperationException If this collection type is not yet known. - */ - default void collectionAdd(E e, T value) { - @SuppressWarnings("unchecked") - Collection c = (Collection) get(e); - if (c != null) { - c.add(value); - } else { - if (getFieldClass().equals(List.class)) { - c = Collections.singletonList(value); - } else if (getFieldClass().equals(Set.class)) { - c = Collections.singleton(value); - } else { - throw new UnsupportedOperationException("Unsupported collection type."); // in case we add e.g. java.util.Queue in future - } - set(e, c); - } - } - /** - * Removes an element from the collection stored in this field. - * @param e Entity - * @param value Value to be added to the collection - * @return Defined by the underlying field. Preferrably it should return deleted object, but it can return - * {@code true / false} indication of removal, or just {@code null}. - * @throws ClassCastException If this field is not a collection. - */ - default Object collectionRemove(E e, T value) { - Collection c = (Collection) get(e); - return c == null ? null : c.remove(value); - } - - /** - * Retrieves a value from the map stored in this field. - * @param e Entity - * @param key Requested key - * @return Object mapped to this key - * @throws ClassCastException If this field is not a map. - */ - default Object mapGet(E e, K key) { - @SuppressWarnings("unchecked") - Map m = (Map) get(e); - return m == null ? null : m.get(key); - } - /** - * Adds a mapping to the map stored in this field. - * @param e Entity - * @param key Key to map - * @param value Mapped value - * @throws ClassCastException If this field is not a map. - */ - default void mapPut(E e, K key, T value) { - @SuppressWarnings("unchecked") - Map m = (Map) get(e); - if (m != null) { - m.put(key, value); - } else { - set(e, Collections.singletonMap(key, value)); - } - } - /** - * Removes a mapping from the map stored in this field. - * @param e Entity - * @param key Key to remove - * @return Object mapped to this key - * @throws ClassCastException If this field is not a map. - */ - default Object mapRemove(E e, K key) { - @SuppressWarnings("unchecked") - Map m = (Map) get(e); - if (m != null) { - return m.remove(key); - } - return null; - } - - /** - * @return Returns the most specific type of this field. - */ - default Class getFieldClass() { return Object.class; } - - /** - * @return If this field is a collection, returns type of its elements; otherwise returns {@code Void} class. - */ - default Class getCollectionElementClass() { return Void.class; } - - /** - * @return If this field is a map, returns type of its keys; otherwise returns {@code Void} class. - */ - default Class getMapKeyClass() { return Void.class; } - - /** - * @return If this field is a map, returns type of its values; otherwise returns {@code Void} class. - */ - default Class getMapValueClass() { return Void.class; } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java b/model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java deleted file mode 100644 index f549ba1d41e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.common; - -import java.util.List; -import java.util.Map; - -public interface EntityWithAttributes { - Map> getAttributes(); - void setAttributes(Map> attributes); - List getAttribute(String name); - void setAttribute(String name, List value); - void removeAttribute(String name); - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/ExpirableEntity.java b/model/map/src/main/java/org/keycloak/models/map/common/ExpirableEntity.java deleted file mode 100644 index 75f5fba7ac7..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/ExpirableEntity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 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.common; - -/** - * This interface provides a way for marking entities that can expire. For example, user sessions are valid only - * for certain amount of time. After that time the entities can be removed from storage/omitted from query results. - * - * Presence of expired entities in the storage should be transparent to layers above the physical one. This can be - * achieved in more ways. Ideal solution is when expired entities never reach Keycloak codebase, however, this may - * not be possible for all storage implementations, therefore, we need to double-check entities validity before they - * reach logical layer, for example, before we turn entity into model. - * - * Implementation of actual removal of the entities from the storage is responsibility of each storage individually. - * - */ -public interface ExpirableEntity extends AbstractEntity { - - /** - * Returns a point in the time (timestamp in milliseconds since The Epoch) when this entity expires. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} if this entity never expires - * or expiration is not known. - */ - Long getExpiration(); - - /** - * Sets a point in the time (timestamp in milliseconds since The Epoch) when this entity expires. - * - * @param expiration a timestamp in milliseconds since The Epoch or {@code null} if this entity never expires. - */ - void setExpiration(Long expiration); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/ExpirationUtils.java b/model/map/src/main/java/org/keycloak/models/map/common/ExpirationUtils.java deleted file mode 100644 index 5ac7ba23a33..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/ExpirationUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 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.common; - -import org.keycloak.common.util.Time; - -public class ExpirationUtils { - - /** - * Checks whether the {@code entity} is expired - * - * @param entity to check - * @param allowInfiniteValues sets how null values are interpreted, if true entity with expiration equal - * to {@code null} is interpreted as never expiring entity, if false entities - * with {@code null} expiration are interpreted as expired entities - * @return true if the {@code entity} is expired (expiration time is in the past or now), false otherwise - */ - public static boolean isExpired(ExpirableEntity entity, boolean allowInfiniteValues) { - Long expiration = entity.getExpiration(); - if (!allowInfiniteValues && expiration == null) return false; - return expiration != null && expiration <= Time.currentTimeMillis(); - } - - public static boolean isNotExpired(Object entity) { - return !isExpired((ExpirableEntity) entity, true); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/HasRealmId.java b/model/map/src/main/java/org/keycloak/models/map/common/HasRealmId.java deleted file mode 100644 index cfcae6f5a63..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/HasRealmId.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022 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.common; - -/** - * Interface for all objects which are bound to a realm and retain reference to its ID. - * @author hmlnarik - */ -public interface HasRealmId { - - /** - * Returns realm ID of the entity. - * @return See description - */ - String getRealmId(); - - /** - * Sets the realm ID of this object. - * @param realmId Realm ID. - */ - void setRealmId(String realmId); -} 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 deleted file mode 100644 index a10a25a2b23..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/Serialization.java +++ /dev/null @@ -1,116 +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.common; - -import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreType; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.JavaType; -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; -import java.io.IOException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -public class Serialization { - - public static final ObjectMapper MAPPER = new ObjectMapper() - .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) - .enable(SerializationFeature.INDENT_OUTPUT) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setVisibility(PropertyAccessor.ALL, Visibility.NONE) - .setVisibility(PropertyAccessor.FIELD, Visibility.ANY) - .activateDefaultTyping(new LaissezFaireSubTypeValidator() /* TODO - see javadoc */, ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS, JsonTypeInfo.As.PROPERTY) - .addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class) - .addMixIn(DeepCloner.class, IgnoredTypeMixIn.class) - ; - - public static final ConcurrentHashMap, ObjectReader> READERS = new ConcurrentHashMap<>(); - public static final ConcurrentHashMap, ObjectWriter> WRITERS = new ConcurrentHashMap<>(); - - @JsonIgnoreType - public class IgnoredTypeMixIn {} - - public abstract class IgnoreUpdatedMixIn { - @JsonIgnore public abstract boolean isUpdated(); - } - - static { - JavaType type = TypeFactory.unknownType(); - JavaType streamType = MAPPER.getTypeFactory().constructParametricType(Stream.class, type); - SimpleModule module = new SimpleModule().addSerializer(new StreamSerializer(streamType, type)); - MAPPER.registerModule(module); - } - - - public static T from(T orig) { - if (orig == null) { - return null; - } - @SuppressWarnings("unchecked") - final Class origClass = (Class) orig.getClass(); - - // Naive solution but will do. - try { - ObjectReader reader = READERS.computeIfAbsent(origClass, MAPPER::readerFor); - ObjectWriter writer = WRITERS.computeIfAbsent(origClass, MAPPER::writerFor); - final T res; - res = reader.readValue(writer.writeValueAsBytes(orig)); - - return res; - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - public static T from(T orig, T target) { - if (orig == null) { - return null; - } - @SuppressWarnings("unchecked") - final Class origClass = (Class) orig.getClass(); - - // Naive solution but will do. - try { - ObjectReader reader = MAPPER.readerForUpdating(target); - ObjectWriter writer = WRITERS.computeIfAbsent(origClass, MAPPER::writerFor); - final T res; - res = reader.readValue(writer.writeValueAsBytes(orig)); - - if (res != target) { - throw new IllegalStateException("Should clone into desired target"); - } - - return res; - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/SessionAttributesUtils.java b/model/map/src/main/java/org/keycloak/models/map/common/SessionAttributesUtils.java deleted file mode 100644 index 993a17ce75a..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/SessionAttributesUtils.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2023 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.common; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.provider.Provider; - -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.Supplier; - -public class SessionAttributesUtils { - private static final AtomicInteger COUNTER_TX = new AtomicInteger(); - - /** - * Returns a new unique counter across whole Keycloak instance - * - * @return unique number - */ - public static int grabNewFactoryIdentifier() { - return COUNTER_TX.getAndIncrement(); - } - - /** - * Used for creating a provider instance only once within one - * KeycloakSession. - *

- * Checks whether there already exists a provider withing session - * attributes for given {@code providerClass} and - * {@code factoryIdentifier}. If exists returns existing provider, - * otherwise creates a new instance using {@code createNew} function. - * - * @param session current Keycloak session - * @param factoryIdentifier unique factory identifier. - * {@link SessionAttributesUtils#grabNewFactoryIdentifier()} - * can be used for obtaining new identifiers. - * @param providerClass class of the requested provider - * @param createNew function that creates a new instance of the provider - * @return an instance of the provider either from session attributes or freshly created. - * @param type of the provider - */ - public static T createProviderIfAbsent(KeycloakSession session, - int factoryIdentifier, - Class providerClass, - Function createNew) { - String uniqueKey = providerClass.getName() + factoryIdentifier; - T provider = session.getAttribute(uniqueKey, providerClass); - - if (provider != null) { - return provider; - } - provider = createNew.apply(session); - - session.setAttribute(uniqueKey, provider); - return provider; - } - - /** - * Used for creating a store instance only once within one - * KeycloakSession. - *

- * Checks whether there already is a store within session attributes - * for given {@code providerClass}, {@code modelType} and - * {@code factoryIdentifier}. If exists returns existing provider, - * otherwise creates a new instance using {@code createNew} supplier. - * - * @param session current Keycloak session - * @param providerType map storage provider class - * @param modelType model class. Can be null if the store is the same - * for all models. - * @param factoryId unique factory identifier. - * {@link SessionAttributesUtils#grabNewFactoryIdentifier()} - * can be used for obtaining new identifiers. - * @param createNew supplier that creates a new instance of the store - * @return an instance of the store either from session attributes or - * freshly created. - * @param entity type - * @param model type - * @param store type - */ - public static > T createMapStorageIfAbsent( - KeycloakSession session, - Class providerType, - Class modelType, - int factoryId, - Supplier createNew) { - String sessionAttributeName = providerType.getName() + "-" + (modelType != null ? modelType.getName() : "") + "-" + factoryId; - - T sessionTransaction = (T) session.getAttribute(sessionAttributeName, MapStorage.class); - if (sessionTransaction == null) { - sessionTransaction = createNew.get(); - session.setAttribute(sessionAttributeName, sessionTransaction); - } - - return sessionTransaction; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/StreamUtils.java b/model/map/src/main/java/org/keycloak/models/map/common/StreamUtils.java deleted file mode 100644 index fe87dea0752..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/StreamUtils.java +++ /dev/null @@ -1,201 +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.common; - -import java.util.Iterator; -import java.util.Objects; -import java.util.Spliterator; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import static java.util.Spliterator.IMMUTABLE; - -/** - * - * @author hmlnarik - */ -public class StreamUtils { - - public static final class Pair { - private final T1 k; - private final T2 v; - - public Pair(T1 k, T2 v) { - this.k = k; - this.v = v; - } - - public T1 getK() { - return k; - } - - public T2 getV() { - return v; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.k); - hash = 97 * hash + Objects.hashCode(this.v); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Pair other = (Pair) obj; - if ( ! Objects.equals(this.k, other.k)) { - return false; - } - if ( ! Objects.equals(this.v, other.v)) { - return false; - } - return true; - } - - @Override - public String toString() { - return "[" + k + ", " + v + "]"; - } - } - - public static abstract class AbstractToPairSpliterator implements Spliterator> { - - protected final Iterator streamIterator; - protected final M mapper; - protected Iterator flatMapIterator; - protected K currentKey; - - public AbstractToPairSpliterator(Stream stream, M mapper) { - this.streamIterator = stream.iterator(); - this.mapper = mapper; - } - - protected abstract void nextKey(); - - @Override - public boolean tryAdvance(Consumer> action) { - if (flatMapIterator != null && flatMapIterator.hasNext()) { - action.accept(new Pair<>(currentKey, flatMapIterator.next())); - return true; - } - - nextKey(); - - if (flatMapIterator != null && flatMapIterator.hasNext()) { - action.accept(new Pair<>(currentKey, flatMapIterator.next())); - return true; - } - return false; - } - - @Override - public Spliterator> trySplit() { - return null; - } - - @Override - public long estimateSize() { - return Long.MAX_VALUE; - } - - @Override - public int characteristics() { - return IMMUTABLE; - } - } - - private static class ToPairSpliterator extends AbstractToPairSpliterator>> { - - public ToPairSpliterator(Stream stream, Function> mapper) { - super(stream, mapper); - } - - @Override - protected void nextKey() { - this.flatMapIterator = null; - while (this.flatMapIterator == null && streamIterator.hasNext()) { - currentKey = streamIterator.next(); - final Stream vStream = mapper.apply(currentKey); - this.flatMapIterator = vStream == null ? null : vStream.iterator(); - } - } - } - - private static class IterableToPairSpliterator extends AbstractToPairSpliterator>> { - - public IterableToPairSpliterator(Stream stream, Function> mapper) { - super(stream, mapper); - } - - @Override - protected void nextKey() { - this.flatMapIterator = null; - while (this.flatMapIterator == null && streamIterator.hasNext()) { - currentKey = streamIterator.next(); - final Iterable vStream = mapper.apply(currentKey); - this.flatMapIterator = vStream == null ? null : vStream.iterator(); - } - } - } - - - /** - * Creates a stream of pairs that join two streams. For each element k from the {@code stream} - * and each element v obtained from the stream returned by the {@code mapper} for k, generates - * a stream of pairs (k, v). - *

- * Effectively performs equivalent of a {@code LEFT INNER JOIN} SQL operation on streams. - * - * @param - * @param - * @param stream - * @param mapper - * @return - */ - public static Stream> leftInnerJoinStream(Stream stream, Function> mapper) { - return StreamSupport.stream(() -> new ToPairSpliterator<>(stream, mapper), IMMUTABLE, false); - } - - /** - * Creates a stream of pairs that join two streams. For each element k from the {@code stream} - * and each element v obtained from the {@code Iterable} returned by the {@code mapper} for k, generates - * a stream of pairs (k, v). - *

- * Effectively performs equivalent of a {@code LEFT INNER JOIN} SQL operation on streams. - * - * @param - * @param - * @param stream - * @param mapper - * @return - */ - public static Stream> leftInnerJoinIterable(Stream stream, Function> mapper) { - return StreamSupport.stream(() -> new IterableToPairSpliterator<>(stream, mapper), IMMUTABLE, false); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/StringKeyConverter.java b/model/map/src/main/java/org/keycloak/models/map/common/StringKeyConverter.java deleted file mode 100644 index 7ae856f0457..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/StringKeyConverter.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.common; - -import java.security.SecureRandom; -import java.util.UUID; - -/** - * Converts given storage key from and to {@code String} representation. - * - * @author hmlnarik - */ -public interface StringKeyConverter { - - /** - * Returns String representation of the key from native representation - * @param key - * @throws IllegalArgumentException if the string format is not recognized - * @throws NullPointerException if the parameter is {@code null} - * @return See above - */ - default String keyToString(K key) { return key == null ? null : key.toString(); } - - /** - * Returns a new unique primary key for the storage that - * this {@link StringKeyConverter} belongs to. The uniqueness - * needs to be guaranteed by e.g. using database sequences or - * using a random value that is proved sufficiently improbable - * to be repeated. - * - * @return - */ - K yieldNewUniqueKey(); - - /** - * Returns native representation of the key from String representation - * @param key - * @throws IllegalArgumentException if the string format is not recognized - * @throws NullPointerException if the parameter is {@code null} - * @return See above - */ - K fromString(String key); - - /** - * Exception-free variant of {@link #fromString} method. - * Returns native representation of the key from String representation, - * or {@code null} if the {@code key} is either {@code null} or invalid. - * @param key - * @return See above - */ - default K fromStringSafe(String key) { - try { - return fromString(key); - } catch (Exception ex) { - return null; - } - } - - class UUIDKey implements StringKeyConverter { - - public static final UUIDKey INSTANCE = new UUIDKey(); - - @Override - public UUID yieldNewUniqueKey() { - return UUID.randomUUID(); - } - - @Override - public UUID fromString(String key) { - return UUID.fromString(key); - } - } - - class StringKey implements StringKeyConverter { - - public static final StringKey INSTANCE = new StringKey(); - - @Override - public String fromString(String key) { - return key; - } - - @Override - public String yieldNewUniqueKey() { - return fromString(UUID.randomUUID().toString()); - } - } - - class ULongKey implements StringKeyConverter { - - public static final ULongKey INSTANCE = new ULongKey(); - - /* - * The random number generator used by this class to create random - * based UUIDs. In a holder class to defer initialization until needed. - */ - private static class Holder { - static final SecureRandom numberGenerator = new SecureRandom(); - } - - @Override - public String keyToString(Long key) { - return Long.toUnsignedString(key); - } - - @Override - public Long fromString(String key) { - return key == null ? null : Long.parseUnsignedLong(key); - } - - @Override - public Long yieldNewUniqueKey() { - return Holder.numberGenerator.nextLong(); - } - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/TimeAdapter.java b/model/map/src/main/java/org/keycloak/models/map/common/TimeAdapter.java deleted file mode 100644 index f5334693774..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/TimeAdapter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2022. 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.common; - -import org.jboss.logging.Logger; - -import java.util.concurrent.TimeUnit; - -/** - * Wrapper for adapters around handling time in seconds. - * - * Will be removed once #11053 has been implemented. - - * @author Alexander Schwartz - */ -public class TimeAdapter { - private static final Logger LOG = Logger.getLogger(TimeAdapter.class); - - /** - * Wrapper to all unsafe downgrading from a Long to an Integer while Keycloak core still handles all time since 1970 as seconds as integers. - * This is safer to use than downgrading in several places as that might be missed once the Core starts to use longs as timestamps as well. - * Simplify/remove once #11053 has been implemented. - */ - - public static int fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(Long timestamp) { - if (timestamp > Integer.MAX_VALUE) { - LOG.warn("Trimmed time value found in the map store; value too large and not supported in core"); - return Integer.MAX_VALUE; - } else { - return timestamp.intValue(); - } - } - - /** - * Wrapper to all upgrading from an Integer to a Long while Keycloak core still handles all time seconds since 1970 as seconds as integers. - * This is safer to use and remove once the Core starts to use longs as timestamps as well. - * Simplify/remove once #11053 has been implemented. - */ - public static long fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(int timestamp) { - return timestamp; - } - - public static Long fromSecondsToMilliseconds(Long seconds) { - if (seconds == null) return null; - return TimeUnit.SECONDS.toMillis(seconds); - } - - public static Long fromMilliSecondsToSeconds(Long milliSeconds) { - if (milliSeconds == null) return null; - return TimeUnit.MILLISECONDS.toSeconds(milliSeconds); - } - - public static Long fromSecondsToMilliseconds(int seconds) { - return fromSecondsToMilliseconds(fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(seconds)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/UndefinedValuesUtils.java b/model/map/src/main/java/org/keycloak/models/map/common/UndefinedValuesUtils.java deleted file mode 100644 index 1ca68e12a84..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/UndefinedValuesUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 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.common; - -import java.util.Collection; -import java.util.Map; - -/** - * This Util class defines conditions when objects can be considered undefined - *
- *
- * For example: - *

    - *
  • {@link String} is undefined if it is {@code null} or {@code empty}
  • - *
  • {@link Collection} is undefined if it is {@code null}, {@code empty} or all items are undefined
  • - *
  • {@link Map} is undefined if it is {@code null}, {@code empty}, or all values are undefined
  • - *
- */ -public class UndefinedValuesUtils { - - /** - * Decides whether the {@link Object o} is defined or not - * - * @param o object to check - * @return true when the {@link Object o} is undefined, false otherwise - */ - public static boolean isUndefined(Object o) { - if (o == null) { - return true; - } else if (o instanceof Collection) { - return isUndefinedCollection((Collection) o); - } else if (o instanceof Map) { - return isUndefinedMap((Map) o); - } else if (o instanceof String) { - return isUndefinedString((String) o); - } else { - return false; - } - } - - private static boolean isUndefinedCollection(Collection collection) { - return collection.isEmpty() || collection.stream().allMatch(UndefinedValuesUtils::isUndefined); - } - - private static boolean isUndefinedMap(Map map) { - return map.isEmpty() || map.values().stream().allMatch(UndefinedValuesUtils::isUndefined); - } - - private static boolean isUndefinedString(String str) { - return str.trim().isEmpty(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/UpdatableEntity.java b/model/map/src/main/java/org/keycloak/models/map/common/UpdatableEntity.java deleted file mode 100644 index 31b51e85674..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/UpdatableEntity.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.common; - -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; - -@IgnoreForEntityImplementationGenerator -public interface UpdatableEntity { - - public static class Impl implements UpdatableEntity { - protected boolean updated; - - @Override - public boolean isUpdated() { - return this.updated; - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - } - - @Override - public void markUpdatedFlag() { - this.updated = true; - } - } - - /** - * Flag signalizing that any of the setters has been meaningfully used. - * @return - */ - boolean isUpdated(); - - /** - * An optional operation clearing the updated flag. Right after using this method, the - * {@link #isUpdated()} would return {@code false}. - */ - default void clearUpdatedFlag() { } - - /** - * An optional operation setting the updated flag. Right after using this method, the - * {@link #isUpdated()} would return {@code true}. - */ - default void markUpdatedFlag() { } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/UuidValidator.java b/model/map/src/main/java/org/keycloak/models/map/common/UuidValidator.java deleted file mode 100644 index f676499100e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/UuidValidator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 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.common; - -import java.util.regex.Pattern; - -/** - * Utility class for validating and converting UUIDs. - * - * @author Stefan Guilhen - */ -public class UuidValidator { - - protected static final Pattern UUID_REGEX_PATTERN = Pattern.compile("^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$"); - - private UuidValidator() {} - - /** - * Validates that the specified {@code id} is a {@code UUID}. - * - * @param id the {@code id} to be validated. - * @return {@code true} if the {@code id} is a {@code UUID}; {@code false} otherwise. - */ - public static boolean isValid(final String id) { - return (id == null) ? false : UUID_REGEX_PATTERN.matcher(id).matches(); - } - - /** - * Validates that the specified {@code id} is a {@code UUID}. If it is, the {@code id} itself is returned. Otherwise, - * it is discarded and a new {@code UUID} is created and returned. - * - * @param id the {@code id} to be validated. - * @return the {@code id} itself if it is a valid {@code UUID}, or a new generated {@code UUID}. - */ - public static String validateAndConvert(final String id) { - return isValid(id) ? id : StringKeyConverter.UUIDKey.INSTANCE.yieldNewUniqueKey().toString(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/DelegateProvider.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/DelegateProvider.java deleted file mode 100644 index 962cd34e9c4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/DelegateProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.common.delegate; - -import org.keycloak.models.map.common.EntityField; - -/** - * Interface for a provider of a delegate of type {@code T}, optionally - * providing the flag on the object been updated. - * @author hmlnarik - */ -public interface DelegateProvider { - /** - * Returns a delegate for and entity for an operation on a field. - * @param isRead {@code true} when the delegate requested for a read operation, false otherwise - * @param field Identification of the field this delegates operates on. While this parameter - * can be any object including {@code null}, if it is a known field, then it is guaranteed to be - * one of the {@code EntityField}s enumerated in one of the {@code Map*EntityFields} enum. - * @return - */ - T getDelegate(boolean isRead, Enum> field, Object... parameters); - - default boolean isUpdated() { return false; } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/EntityFieldDelegate.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/EntityFieldDelegate.java deleted file mode 100644 index a8f6c39fe05..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/EntityFieldDelegate.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.keycloak.models.map.common.delegate; - - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; - -public interface EntityFieldDelegate extends UpdatableEntity { - - public abstract class WithEntity implements EntityFieldDelegate { - protected final E entity; - - public WithEntity(E entity) { - this.entity = entity; - } - - @Override - public > & EntityField> Object get(EF field) { - return field.get(entity); - } - - @Override - public > & EntityField> void set(EF field, T value) { - field.set(entity, value); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapRemove(EF field, K key) { - return field.mapRemove(entity, key); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void mapPut(EF field, K key, T value) { - field.mapPut(entity, key, value); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object mapGet(EF field, K key) { - return field.mapGet(entity, key); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object collectionRemove(EF field, T value) { - return field.collectionRemove(entity, value); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> void collectionAdd(EF field, T value) { - field.collectionAdd(entity, value); - } - - @Override - public boolean isUpdated() { - return entity.isUpdated(); - } - - @Override - public void markUpdatedFlag() { - entity.markUpdatedFlag(); - } - - @Override - public void clearUpdatedFlag() { - entity.clearUpdatedFlag(); - } - - @Override - public String toString() { - return "&" + String.valueOf(entity); - } - } - - // Non-collection values - > & EntityField> Object get(EF field); - > & EntityField> void set(EF field, T value); - - > & EntityField> void collectionAdd(EF field, T value); - > & EntityField> Object collectionRemove(EF field, T value); - - > & EntityField> Object mapGet(EF field, K key); - > & EntityField> void mapPut(EF field, K key, T value); - > & EntityField> Object mapRemove(EF field, K key); - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/HasDelegateProvider.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/HasDelegateProvider.java deleted file mode 100644 index fdce342189e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/HasDelegateProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2022 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.common.delegate; - -public interface HasDelegateProvider { - DelegateProvider getDelegateProvider(); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/HasEntityFieldDelegate.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/HasEntityFieldDelegate.java deleted file mode 100644 index 1b2aadf5626..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/HasEntityFieldDelegate.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.keycloak.models.map.common.delegate; - -public interface HasEntityFieldDelegate { - EntityFieldDelegate getEntityFieldDelegate(); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/LazilyInitialized.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/LazilyInitialized.java deleted file mode 100644 index 012d7e74cfa..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/LazilyInitialized.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.common.delegate; - -import java.util.concurrent.atomic.AtomicMarkableReference; -import java.util.function.Supplier; - -/** - * - * @author hmlnarik - */ -public class LazilyInitialized { - - private final Supplier supplier; - - private final AtomicMarkableReference supplierRef = new AtomicMarkableReference<>(null, false); - - public LazilyInitialized(Supplier supplier) { - this.supplier = supplier; - } - - public T get() { - if (! isInitialized()) { - supplierRef.compareAndSet(null, supplier == null ? null : supplier.get(), false, true); - } - return supplierRef.getReference(); - } - - /** - * Returns {@code true} if the reference to the object has been initialized - * @return - */ - public boolean isInitialized() { - return supplierRef.isMarked(); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/LazyDelegateProvider.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/LazyDelegateProvider.java deleted file mode 100644 index 0a723983239..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/LazyDelegateProvider.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.common.delegate; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; -import java.util.concurrent.atomic.AtomicMarkableReference; -import java.util.function.Supplier; - -/** - * - * @author hmlnarik - */ -public class LazyDelegateProvider implements DelegateProvider { - - protected final LazilyInitialized delegateSupplier; - - public LazyDelegateProvider(Supplier delegateSupplier) { - this.delegateSupplier = new LazilyInitialized<>(delegateSupplier); - } - - @Override - public T getDelegate(boolean isRead, Enum> field, Object... parameters) { - T ref = delegateSupplier.get(); - if (ref == null) { - throw new IllegalStateException("Invalid delegate obtained"); - } - return ref; - } - - @Override - public boolean isUpdated() { - if (delegateSupplier.isInitialized()) { - T d = delegateSupplier.get(); - return d instanceof UpdatableEntity ? ((UpdatableEntity) d).isUpdated() : false; - } - return false; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProvider.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProvider.java deleted file mode 100644 index cc8f75dfc39..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProvider.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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.common.delegate; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.tree.TreeStorageNodeInstance; -import org.keycloak.models.map.storage.tree.TreeStorageNodePrescription.FieldContainedStatus; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -public class PerFieldDelegateProvider implements EntityFieldDelegate { - - private final TreeStorageNodeInstance node; - - private final V entity; - - private final LazilyInitialized lowerEntity; - - public PerFieldDelegateProvider(TreeStorageNodeInstance node, V entity, Supplier fallbackProvider) { - this.node = node; - this.entity = entity; - this.lowerEntity = new LazilyInitialized<>(fallbackProvider); - } - - public PerFieldDelegateProvider(TreeStorageNodeInstance.WithEntity nodeWithEntity, Supplier fallbackProvider) { - this(nodeWithEntity.getNode(), nodeWithEntity.getEntity(), fallbackProvider); - } - - private V getEntityFromDescendantNode() { - final V res = lowerEntity.get(); - Objects.requireNonNull(res, () -> "Descendant entity not found for node " + node); - return res; - } - - @Override - public > & EntityField> Object mapRemove(EF field, K key) { - Objects.requireNonNull(key, "Key must not be null"); - boolean needsSetEntity = false; - boolean needsSetLowerEntity = false; - - switch (node.isCacheFor(field, key)) { - case FULLY: - needsSetEntity = true; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - switch (node.isPrimarySourceFor(field, key)) { - case FULLY: - needsSetEntity = true; - needsSetLowerEntity = false; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - Object res = null; - if (needsSetEntity) { - res = field.mapRemove(entity, key); - } - if (needsSetLowerEntity) { - res = field.mapRemove(getEntityFromDescendantNode(), key); - } - - return res; - } - - @Override - public > & EntityField> void mapPut(EF field, K key, T value) { - Objects.requireNonNull(key, "Key must not be null"); - boolean needsSetEntity = false; - boolean needsSetLowerEntity = false; - - switch (node.isCacheFor(field, key)) { - case FULLY: - needsSetEntity = true; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - switch (node.isPrimarySourceFor(field, key)) { - case FULLY: - needsSetEntity = true; - needsSetLowerEntity = false; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - if (needsSetEntity) { - field.mapPut(entity, key, value); - } - if (needsSetLowerEntity) { - field.mapPut(getEntityFromDescendantNode(), key, value); - } - } - - @Override - public > & EntityField> Object mapGet(EF field, K key) { - Objects.requireNonNull(key, "Key must not be null"); - switch (node.isCacheFor(field, key).max(() -> node.isPrimarySourceFor(field, key))) { - case FULLY: - return field.mapGet(entity, key); - case NOT_CONTAINED: - return field.mapGet(getEntityFromDescendantNode(), key); - } - throw new IllegalStateException("Field is not determined: " + field); - } - - @Override - public > & EntityField> Object collectionRemove(EF field, T value) { - boolean needsSetEntity = false; - boolean needsSetLowerEntity = false; - - switch (node.isCacheFor(field, value)) { - case FULLY: - needsSetEntity = true; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - switch (node.isPrimarySourceFor(field, value)) { - case FULLY: - needsSetEntity = true; - needsSetLowerEntity = false; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - Object res = null; - if (needsSetEntity) { - res = field.collectionRemove(entity, value); - } - if (needsSetLowerEntity) { - res = field.collectionRemove(getEntityFromDescendantNode(), value); - } - - return res; - } - - @Override - public > & EntityField> void collectionAdd(EF field, T value) { - boolean needsSetEntity = false; - boolean needsSetLowerEntity = false; - - switch (node.isCacheFor(field, null)) { - case FULLY: - needsSetEntity = true; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - switch (node.isPrimarySourceFor(field, null)) { - case FULLY: - needsSetEntity = true; - needsSetLowerEntity = false; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - if (needsSetEntity) { - field.collectionAdd(entity, value); - } - if (needsSetLowerEntity) { - field.collectionAdd(getEntityFromDescendantNode(), value); - } - } - - private final Collector> ENTRY_TO_HASH_MAP_OVERRIDING_KEYS_COLLECTOR = Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (Object a, Object b) -> b, HashMap::new); - - @Override - @SuppressWarnings("unchecked") - public > & EntityField> Object get(EF field) { - switch (node.isCacheFor(field, null).max(() -> node.isPrimarySourceFor(field, null))) { - case FULLY: - return field.get(entity); - case NOT_CONTAINED: - return field.get(getEntityFromDescendantNode()); - } - - // It has to be partial field. The only supported partial field is a Map - if (field.getMapKeyClass() == Void.class) { - throw new IllegalStateException("Field " + field + " expected to be a map but is " + field.getFieldClass()); - } - - Map m1 = (Map) field.get(entity); - Map m2 = (Map) field.get(getEntityFromDescendantNode()); - if (m1 == null) { - return m2 == null ? null : new HashMap<>(m2); - } - Predicate> isInNode = me -> node.isCacheFor(field, me.getKey()) - .max(() -> node.isPrimarySourceFor(field, me.getKey())) == FieldContainedStatus.FULLY; - Stream> s = m1.entrySet().stream() - .filter(isInNode); - if (m2 == null) { - return s.collect(ENTRY_TO_HASH_MAP_OVERRIDING_KEYS_COLLECTOR); - } - - return Stream.concat(s, m2.entrySet().stream().filter(isInNode.negate())) - .collect(ENTRY_TO_HASH_MAP_OVERRIDING_KEYS_COLLECTOR); - } - - @Override - public > & EntityField> void set(EF field, T value) { - boolean needsSetEntity = false; - boolean needsSetLowerEntity = false; - - switch (node.isCacheFor(field, null)) { - case FULLY: - needsSetEntity = true; - break; - - case PARTIALLY: - needsSetEntity = true; - needsSetLowerEntity = true; - break; - } - - switch (node.isPrimarySourceFor(field, null)) { - case FULLY: - needsSetEntity = true; - needsSetLowerEntity = false; - break; - - case PARTIALLY: - needsSetEntity = true; - needsSetLowerEntity = true; - break; - - case NOT_CONTAINED: - needsSetLowerEntity = true; - break; - } - - if (needsSetEntity) { - field.set(entity, value); - } - if (needsSetLowerEntity) { - field.set(getEntityFromDescendantNode(), value); - } - } - - @Override - public boolean isUpdated() { - return entity instanceof UpdatableEntity ? ((UpdatableEntity) entity).isUpdated() : false; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/common/delegate/SimpleDelegateProvider.java b/model/map/src/main/java/org/keycloak/models/map/common/delegate/SimpleDelegateProvider.java deleted file mode 100644 index 65b97a4a370..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/common/delegate/SimpleDelegateProvider.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.common.delegate; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; - -/** - * - * @author hmlnarik - */ -public class SimpleDelegateProvider implements DelegateProvider { - - private final T delegate; - - public SimpleDelegateProvider(T delegate) { - this.delegate = delegate; - } - - @Override - public T getDelegate(boolean isRead, Enum> field, Object... parameters) { - return this.delegate; - } - - @Override - public boolean isUpdated() { - return this.delegate.isUpdated(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/credential/DefaultMapSubjectCredentialManagerEntity.java b/model/map/src/main/java/org/keycloak/models/map/credential/DefaultMapSubjectCredentialManagerEntity.java deleted file mode 100644 index e86ef452feb..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/credential/DefaultMapSubjectCredentialManagerEntity.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022. 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.credential; - -import org.keycloak.credential.CredentialInput; - -import java.util.List; -import java.util.stream.Stream; - -/** - * Standard implementation for a {@link MapSubjectCredentialManagerEntity} where the store doesn't provide - * validation of credentials. - * - * @author Alexander Schwartz - */ -public class DefaultMapSubjectCredentialManagerEntity implements MapSubjectCredentialManagerEntity { - @Override - public void validateCredentials(List inputs) { - } - - @Override - public boolean updateCredential(CredentialInput input) { - return false; - } - - @Override - public boolean isConfiguredFor(String type) { - return false; - } - - @Override - public Stream getDisableableCredentialTypesStream() { - return Stream.empty(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/credential/MapSubjectCredentialManagerEntity.java b/model/map/src/main/java/org/keycloak/models/map/credential/MapSubjectCredentialManagerEntity.java deleted file mode 100644 index 793b72c0197..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/credential/MapSubjectCredentialManagerEntity.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2022. 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.credential; - -import org.keycloak.credential.CredentialInput; - -import java.util.List; -import java.util.stream.Stream; - -/** - * Interface for credential management in entities in the map storage. - * - * @author Alexander Schwartz - */ -public interface MapSubjectCredentialManagerEntity { - - /** - * Validate the credentials of a user. - * Will remove all inputs from the list that have been successfully validated, all remaining entries - * weren't validated. An empty list signals to the caller that authentication has completed successfully. - * - * @param inputs Credential inputs as provided by a user - */ - void validateCredentials(List inputs); - - /** - * Update the credentials for a user with the input provided by the user for this store. - * @param input new credentials as provided by the user - * @return true if the credential has been updated successfully, false otherwise. False might indicate that the - * credential type isn't supported of the new credentials aren't valid. - */ - boolean updateCredential(CredentialInput input); - - /** - * Check if the entity is configured for the given credential type. - * @param type credential type - */ - boolean isConfiguredFor(String type); - - /** - * List the credential types that can be disabled for this user. - * @return Stream of credential types - */ - Stream getDisableableCredentialTypesStream(); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/credential/MapUserCredentialManager.java b/model/map/src/main/java/org/keycloak/models/map/credential/MapUserCredentialManager.java deleted file mode 100644 index 0b6eb1d2394..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/credential/MapUserCredentialManager.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2022. 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.credential; - -import org.keycloak.common.util.reflections.Types; -import org.keycloak.credential.CredentialInput; -import org.keycloak.credential.CredentialInputUpdater; -import org.keycloak.credential.CredentialInputValidator; -import org.keycloak.credential.CredentialModel; -import org.keycloak.credential.CredentialProvider; -import org.keycloak.credential.CredentialProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.SubjectCredentialManager; -import org.keycloak.models.UserModel; -import org.keycloak.models.map.user.MapUserCredentialEntity; -import org.keycloak.models.map.user.MapUserEntity; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; - -/** - * Handling credentials for a given user. - * - * This serves as a wrapper to specific strategies. The wrapping code implements the logic for {@link CredentialInputUpdater}s - * and {@link CredentialInputValidator}s. - * - * @author Alexander Schwartz - */ -public class MapUserCredentialManager implements SubjectCredentialManager { - - private final UserModel user; - private final KeycloakSession session; - private final RealmModel realm; - private final MapUserEntity entity; - - public MapUserCredentialManager(KeycloakSession session, RealmModel realm, UserModel user, MapUserEntity entity) { - this.user = user; - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean isValid(List inputs) { - if (!isValid(user)) { - return false; - } - - List toValidate = new LinkedList<>(inputs); - - entity.credentialManager().validateCredentials(toValidate); - - getCredentialProviders(session, CredentialInputValidator.class) - .forEach(validator -> validate(realm, user, toValidate, validator)); - - return toValidate.isEmpty(); - } - - @Override - public boolean updateCredential(CredentialInput input) { - return entity.credentialManager().updateCredential(input) || - getCredentialProviders(session, CredentialInputUpdater.class) - .filter(updater -> updater.supportsCredentialType(input.getType())) - .anyMatch(updater -> updater.updateCredential(realm, user, input)); - } - - @Override - public void updateStoredCredential(CredentialModel cred) { - throwExceptionIfInvalidUser(user); - entity.getCredential(cred.getId()).ifPresent(c -> { - c.setCreatedDate(cred.getCreatedDate()); - c.setUserLabel(cred.getUserLabel()); - c.setType(cred.getType()); - c.setSecretData(cred.getSecretData()); - c.setCredentialData(cred.getCredentialData()); - }); - } - - @Override - public CredentialModel createStoredCredential(CredentialModel cred) { - throwExceptionIfInvalidUser(user); - MapUserCredentialEntity credentialEntity = MapUserCredentialEntity.fromModel(cred); - - if (entity.getCredential(cred.getId()).isPresent()) { - throw new ModelDuplicateException("A CredentialModel with given id already exists"); - } - - entity.addCredential(credentialEntity); - - return MapUserCredentialEntity.toModel(credentialEntity); - } - - @Override - public boolean removeStoredCredentialById(String id) { - throwExceptionIfInvalidUser(user); - return entity.removeCredential(id); - } - - @Override - public CredentialModel getStoredCredentialById(String id) { - return entity.getCredential(id).map(MapUserCredentialEntity::toModel).orElse(null); - } - - @Override - public Stream getStoredCredentialsStream() { - return Optional.ofNullable(entity.getCredentials()).orElse(Collections.emptyList()).stream() - .map(MapUserCredentialEntity::toModel); - } - - @Override - public Stream getStoredCredentialsByTypeStream(String type) { - return getStoredCredentialsStream() - .filter(credential -> Objects.equals(type, credential.getType())); - } - - @Override - public CredentialModel getStoredCredentialByNameAndType(String name, String type) { - return getStoredCredentialsStream() - .filter(credential -> Objects.equals(name, credential.getUserLabel())) - .findFirst().orElse(null); - } - - @Override - public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) { - throwExceptionIfInvalidUser(user); - return entity.moveCredential(id, newPreviousCredentialId); - } - - @Override - public void updateCredentialLabel(String credentialId, String userLabel) { - throwExceptionIfInvalidUser(user); - CredentialModel credential = getStoredCredentialById(credentialId); - credential.setUserLabel(userLabel); - updateStoredCredential(credential); - } - - @Override - public void disableCredentialType(String credentialType) { - getCredentialProviders(session, CredentialInputUpdater.class) - .filter(updater -> updater.supportsCredentialType(credentialType)) - .forEach(updater -> updater.disableCredentialType(realm, user, credentialType)); - } - - @Override - public Stream getDisableableCredentialTypesStream() { - return Stream.concat(entity.credentialManager().getDisableableCredentialTypesStream(), - getCredentialProviders(session, CredentialInputUpdater.class) - .flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user))) - .distinct(); - } - - @Override - public boolean isConfiguredFor(String type) { - return entity.credentialManager().isConfiguredFor(type) || - getCredentialProviders(session, CredentialInputValidator.class) - .anyMatch(validator -> validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)); - } - - @Override - @Deprecated - public boolean isConfiguredLocally(String type) { - throw new IllegalArgumentException("this is not supported for map storage"); - } - - @Override - @Deprecated - public Stream getConfiguredUserStorageCredentialTypesStream() { - // used in the old admin console for users to determine if a password is set for a user - // not used in the new admin console - return Stream.empty(); - } - - @Override - @Deprecated - public CredentialModel createCredentialThroughProvider(CredentialModel model) { - // this is still called when importing/creating a user via RepresentationToModel.createCredentials - throwExceptionIfInvalidUser(user); - return session.getKeycloakSessionFactory() - .getProviderFactoriesStream(CredentialProvider.class) - .map(f -> session.getProvider(CredentialProvider.class, f.getId())) - .filter(provider -> Objects.equals(provider.getType(), model.getType())) - .map(cp -> cp.createCredential(realm, user, cp.getCredentialFromModel(model))) - .findFirst() - .orElse(null); - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean isValid(UserModel user) { - Objects.requireNonNull(user); - return user.getServiceAccountClientLink() == null; - } - - private void validate(RealmModel realm, UserModel user, List toValidate, CredentialInputValidator validator) { - toValidate.removeIf(input -> validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)); - } - - private static Stream getCredentialProviders(KeycloakSession session, Class type) { - //noinspection unchecked - return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class) - .filter(f -> Types.supports(type, f, CredentialProviderFactory.class)) - .map(f -> (T) session.getProvider(CredentialProvider.class, f.getId())); - } - - private void throwExceptionIfInvalidUser(UserModel user) { - if (!isValid(user)) { - throw new RuntimeException("You can not manage credentials for this user"); - } - } - -} - diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/ImportKeycloakSession.java b/model/map/src/main/java/org/keycloak/models/map/datastore/ImportKeycloakSession.java deleted file mode 100644 index 5c0cd117ded..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/ImportKeycloakSession.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2022 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.datastore; - -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.ClientProvider; -import org.keycloak.models.ClientScopeProvider; -import org.keycloak.models.ClientScopeSpi; -import org.keycloak.models.ClientSpi; -import org.keycloak.models.GroupProvider; -import org.keycloak.models.GroupSpi; -import org.keycloak.models.KeyManager; -import org.keycloak.models.KeycloakContext; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.KeycloakTransactionManager; -import org.keycloak.models.ModelException; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.RealmSpi; -import org.keycloak.models.RoleProvider; -import org.keycloak.models.RoleSpi; -import org.keycloak.models.SingleUseObjectProvider; -import org.keycloak.models.ThemeManager; -import org.keycloak.models.TokenManager; -import org.keycloak.models.UserLoginFailureProvider; -import org.keycloak.models.UserProvider; -import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.UserSpi; -import org.keycloak.models.map.client.MapClientProvider; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.clientscope.MapClientScopeProvider; -import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.group.MapGroupProvider; -import org.keycloak.models.map.group.MapGroupProviderFactory; -import org.keycloak.models.map.realm.MapRealmProvider; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.role.MapRoleProvider; -import org.keycloak.models.map.role.MapRoleProviderFactory; -import org.keycloak.models.map.user.MapUserProvider; -import org.keycloak.models.map.user.MapUserProviderFactory; -import org.keycloak.provider.InvalidationHandler; -import org.keycloak.provider.Provider; -import org.keycloak.provider.Spi; -import org.keycloak.services.clientpolicy.ClientPolicyManager; -import org.keycloak.sessions.AuthenticationSessionProvider; -import org.keycloak.vault.VaultTranscriber; - -import java.lang.reflect.InvocationTargetException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; - -/** - * This implementation of {@link KeycloakSession} wraps an existing session and directs all calls to the datastore provider - * to a separate {@link KeycloakSessionFactory}. - * This allows it to create instantiate different storage providers during import. - * - * @author Alexander Schwartz - */ -public class ImportKeycloakSession implements KeycloakSession { - - private static final Logger LOG = Logger.getLogger(ImportKeycloakSession.class); - - private final KeycloakSessionFactory factory; - private final KeycloakSession session; - private final MapRealmProvider realmProvider; - private final MapClientProvider clientProvider; - private final MapClientScopeProvider clientScopeProvider; - private final MapGroupProvider groupProvider; - private final MapRoleProvider roleProvider; - private final MapUserProvider userProvider; - - private final Set> providerFactories = new HashSet<>(); - - public ImportKeycloakSession(ImportSessionFactoryWrapper factory, KeycloakSession session) { - this.factory = factory; - this.session = session; - realmProvider = createProvider(RealmSpi.class, MapRealmProviderFactory.class); - clientProvider = createProvider(ClientSpi.class, MapClientProviderFactory.class); - clientScopeProvider = createProvider(ClientScopeSpi.class, MapClientScopeProviderFactory.class); - groupProvider = createProvider(GroupSpi.class, MapGroupProviderFactory.class); - roleProvider = createProvider(RoleSpi.class, MapRoleProviderFactory.class); - userProvider = createProvider(UserSpi.class, MapUserProviderFactory.class); - } - - private

P createProvider(Class spi, Class> providerFactoryClass) { - try { - AbstractMapProviderFactory providerFactory = providerFactoryClass.getConstructor().newInstance(); - providerFactory.init(Config.scope(spi.getDeclaredConstructor().newInstance().getName(), providerFactory.getId())); - providerFactories.add(providerFactory); - return providerFactory.create(this); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | - InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - @Override - public KeycloakContext getContext() { - return session.getContext(); - } - - @Override - public KeycloakTransactionManager getTransactionManager() { - return session.getTransactionManager(); - } - - @Override - public T getProvider(Class clazz) { - LOG.warnf("Calling getProvider(%s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace()); - return session.getProvider(clazz); - } - - @Override - public T getProvider(Class clazz, String id) { - LOG.warnf("Calling getProvider(%s, %s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), id, getShortStackTrace()); - return session.getProvider(clazz, id); - } - - @Override - public T getComponentProvider(Class clazz, String componentId) { - LOG.warnf("Calling getComponentProvider(%s, %s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), componentId, getShortStackTrace()); - return session.getComponentProvider(clazz, componentId); - } - - @Override - public T getComponentProvider(Class clazz, String componentId, Function modelGetter) { - LOG.warnf("Calling getComponentProvider(%s, %s, ...) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), componentId, getShortStackTrace()); - return session.getComponentProvider(clazz, componentId, modelGetter); - } - - @Override - public T getProvider(Class clazz, ComponentModel componentModel) { - LOG.warnf("Calling getProvider(%s, ...) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace()); - return session.getProvider(clazz, componentModel); - } - - @Override - public Set listProviderIds(Class clazz) { - LOG.warnf("Calling listProviderIds(%s, ...) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace()); - return session.listProviderIds(clazz); - } - - @Override - public Set getAllProviders(Class clazz) { - LOG.warnf("Calling getAllProviders(%s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace()); - return session.getAllProviders(clazz); - } - - @Override - public Class getProviderClass(String providerClassName) { - LOG.warnf("Calling getProviderClass(%s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", providerClassName, getShortStackTrace()); - return session.getProviderClass(providerClassName); - } - - @Override - public Object getAttribute(String attribute) { - return session.getAttribute(attribute); - } - - @Override - public T getAttribute(String attribute, Class clazz) { - return session.getAttribute(attribute, clazz); - } - - @Override - public T getAttributeOrDefault(String attribute, T defaultValue) { - return session.getAttributeOrDefault(attribute, defaultValue); - } - - @Override - public Object removeAttribute(String attribute) { - return session.removeAttribute(attribute); - } - - @Override - public void setAttribute(String name, Object value) { - session.setAttribute(name, value); - } - - @Override - public Map getAttributes() { - return session.getAttributes(); - } - - @Override - public void invalidate(InvalidationHandler.InvalidableObjectType type, Object... ids) { - // For now, forward the invalidations only to those providers created specifically for this import session. - providerFactories.stream() - .filter(InvalidationHandler.class::isInstance) - .map(InvalidationHandler.class::cast) - .forEach(ih -> ih.invalidate(this, type, ids)); - } - - @Override - public void enlistForClose(Provider provider) { - session.enlistForClose(provider); - } - - @Override - public KeycloakSessionFactory getKeycloakSessionFactory() { - return factory; - } - - @Override - public RealmProvider realms() { - return realmProvider; - } - - @Override - public ClientProvider clients() { - return clientProvider; - } - - @Override - public ClientScopeProvider clientScopes() { - return clientScopeProvider; - } - - @Override - public GroupProvider groups() { - return groupProvider; - } - - @Override - public RoleProvider roles() { - return roleProvider; - } - - @Override - public UserSessionProvider sessions() { - throw new ModelException("not supported yet"); - } - - @Override - public UserLoginFailureProvider loginFailures() { - throw new ModelException("not supported yet"); - } - - @Override - public AuthenticationSessionProvider authenticationSessions() { - throw new ModelException("not supported yet"); - } - - @Override - public SingleUseObjectProvider singleUseObjects() { - throw new ModelException("not supported yet"); - } - - @Override - public void close() { - session.close(); - } - - @Override - public UserProvider users() { - return userProvider; - } - - @Override - public KeyManager keys() { - throw new ModelException("not supported"); - } - - @Override - public ThemeManager theme() { - throw new ModelException("not supported"); - } - - @Override - public TokenManager tokens() { - throw new ModelException("not supported"); - } - - @Override - public VaultTranscriber vault() { - throw new ModelException("not supported"); - } - - @Override - public ClientPolicyManager clientPolicy() { - return session.clientPolicy(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/ImportSessionFactoryWrapper.java b/model/map/src/main/java/org/keycloak/models/map/datastore/ImportSessionFactoryWrapper.java deleted file mode 100644 index a1a7afab2e3..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/ImportSessionFactoryWrapper.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2022 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.datastore; - -import org.keycloak.Config; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderEvent; -import org.keycloak.provider.ProviderEventListener; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -import java.util.List; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Stream; - -/** - * This wraps an existing KeycloakSessionFactory and redirects all calls to a {@link MapStorageProvider} to - * {@link org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProvider}. This allows all operations to - * be in-memory. The final contents of the store can then be copied over to the final store once the import is complete. - *

- * For this to work, the CHM provider needs to be registered as a provider. - * - * @author Alexander Schwartz - */ -public class ImportSessionFactoryWrapper implements KeycloakSessionFactory { - private ConcurrentHashMapStorageProviderFactory concurrentHashMapStorageProviderFactory; - - @Override - public KeycloakSession create() { - concurrentHashMapStorageProviderFactory = new ConcurrentHashMapStorageProviderFactory(); - concurrentHashMapStorageProviderFactory.init(Config.scope(MapStorageSpi.NAME, concurrentHashMapStorageProviderFactory.getId())); - return new ImportKeycloakSession(this, keycloakSessionFactory.create()); - } - - @Override - public Set getSpis() { - return keycloakSessionFactory.getSpis(); - } - - @Override - public Spi getSpi(Class providerClass) { - return keycloakSessionFactory.getSpi(providerClass); - } - - @Override - public ProviderFactory getProviderFactory(Class clazz) { - if (clazz == MapStorageProvider.class) { - return keycloakSessionFactory.getProviderFactory(clazz, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID); - } - return keycloakSessionFactory.getProviderFactory(clazz); - } - - @Override - public ProviderFactory getProviderFactory(Class clazz, String id) { - if (clazz == MapStorageProvider.class) { - return (ProviderFactory) concurrentHashMapStorageProviderFactory; - } - return keycloakSessionFactory.getProviderFactory(clazz, id); - } - - @Override - public ProviderFactory getProviderFactory(Class clazz, String realmId, String componentId, Function modelGetter) { - return keycloakSessionFactory.getProviderFactory(clazz, realmId, componentId, modelGetter); - } - - @Override - public Stream getProviderFactoriesStream(Class clazz) { - return keycloakSessionFactory.getProviderFactoriesStream(clazz); - } - - @Override - public long getServerStartupTimestamp() { - return keycloakSessionFactory.getServerStartupTimestamp(); - } - - @Override - public void close() { - keycloakSessionFactory.close(); - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - keycloakSessionFactory.invalidate(session, type, params); - } - - @Override - public void register(ProviderEventListener listener) { - keycloakSessionFactory.register(listener); - } - - @Override - public void unregister(ProviderEventListener listener) { - keycloakSessionFactory.unregister(listener); - } - - @Override - public void publish(ProviderEvent event) { - keycloakSessionFactory.publish(event); - } - - private final KeycloakSessionFactory keycloakSessionFactory; - - public ImportSessionFactoryWrapper(KeycloakSessionFactory keycloakSessionFactory) { - this.keycloakSessionFactory = keycloakSessionFactory; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapDatastoreProvider.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapDatastoreProvider.java deleted file mode 100644 index 27d011e20f8..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapDatastoreProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2022 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.datastore; - -import org.keycloak.models.ClientProvider; -import org.keycloak.models.ClientScopeProvider; -import org.keycloak.models.GroupProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.RoleProvider; -import org.keycloak.models.SingleUseObjectProvider; -import org.keycloak.models.UserLoginFailureProvider; -import org.keycloak.models.UserProvider; -import org.keycloak.models.UserSessionProvider; -import org.keycloak.sessions.AuthenticationSessionProvider; -import org.keycloak.storage.DatastoreProvider; -import org.keycloak.storage.ExportImportManager; - -public class MapDatastoreProvider implements DatastoreProvider { - - private final KeycloakSession session; - - public MapDatastoreProvider(KeycloakSession session) { - this.session = session; - } - - @Override - public void close() { - } - - @Override - public AuthenticationSessionProvider authSessions() { - return session.getProvider(AuthenticationSessionProvider.class); - } - - @Override - public ClientScopeProvider clientScopes() { - return session.getProvider(ClientScopeProvider.class); - } - - @Override - public ClientProvider clients() { - return session.getProvider(ClientProvider.class); - } - - @Override - public GroupProvider groups() { - return session.getProvider(GroupProvider.class); - } - - @Override - public UserLoginFailureProvider loginFailures() { - return session.getProvider(UserLoginFailureProvider.class); - } - - @Override - public RealmProvider realms() { - return session.getProvider(RealmProvider.class); - } - - @Override - public RoleProvider roles() { - return session.getProvider(RoleProvider.class); - } - - @Override - public SingleUseObjectProvider singleUseObjects() { - return session.getProvider(SingleUseObjectProvider.class); - } - - @Override - public UserProvider users() { - return session.getProvider(UserProvider.class); - } - - @Override - public UserSessionProvider userSessions() { - return session.getProvider(UserSessionProvider.class); - } - - @Override - public ExportImportManager getExportImportManager() { - return new MapExportImportManager(session); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapDatastoreProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapDatastoreProviderFactory.java deleted file mode 100644 index 38625adf344..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapDatastoreProviderFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 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.datastore; - -import org.keycloak.Config.Scope; -import org.keycloak.common.Profile; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.storage.DatastoreProvider; -import org.keycloak.storage.DatastoreProviderFactory; - -public class MapDatastoreProviderFactory implements DatastoreProviderFactory, EnvironmentDependentProviderFactory { - - private static final String PROVIDER_ID = "map"; - - @Override - public DatastoreProvider create(KeycloakSession session) { - return new MapDatastoreProvider(session); - } - - @Override - public void init(Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java deleted file mode 100644 index fc7968d73ac..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java +++ /dev/null @@ -1,1660 +0,0 @@ -/* - * Copyright 2022 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.datastore; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.common.enums.SslRequired; -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.component.ComponentModel; -import org.keycloak.deployment.DeployedConfigurationsManager; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.ClientProvider; -import org.keycloak.models.ClientScopeProvider; -import org.keycloak.models.GroupProvider; -import org.keycloak.models.ImpersonationConstants; -import org.keycloak.models.ModelException; -import org.keycloak.exportimport.ExportAdapter; -import org.keycloak.exportimport.ExportOptions; -import org.keycloak.keys.KeyProvider; -import org.keycloak.migration.MigrationProvider; -import org.keycloak.migration.migrators.MigrationUtils; -import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.AuthenticatorConfigModel; -import org.keycloak.models.BrowserSecurityHeaders; -import org.keycloak.models.CibaConfig; -import org.keycloak.models.ClaimMask; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.Constants; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.OAuth2DeviceConfig; -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.ParConfig; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.RoleProvider; -import org.keycloak.models.ScopeContainerModel; -import org.keycloak.models.UserConsentModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserProvider; -import org.keycloak.models.WebAuthnPolicy; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.DefaultAuthenticationFlows; -import org.keycloak.models.utils.DefaultKeyProviders; -import org.keycloak.models.utils.DefaultRequiredActions; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.RepresentationToModel; -import org.keycloak.partialimport.PartialImportResults; -import org.keycloak.protocol.oidc.OIDCConfigAttributes; -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.representations.idm.ApplicationRepresentation; -import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; -import org.keycloak.representations.idm.AuthenticationFlowRepresentation; -import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; -import org.keycloak.representations.idm.ClaimRepresentation; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ClientScopeRepresentation; -import org.keycloak.representations.idm.ClientTemplateRepresentation; -import org.keycloak.representations.idm.ComponentExportRepresentation; -import org.keycloak.representations.idm.ComponentRepresentation; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.GroupRepresentation; -import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -import org.keycloak.representations.idm.OAuthClientRepresentation; -import org.keycloak.representations.idm.PartialImportRepresentation; -import org.keycloak.representations.idm.ProtocolMapperRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.RequiredActionProviderRepresentation; -import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.ScopeMappingRepresentation; -import org.keycloak.representations.idm.UserConsentRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.storage.ExportImportManager; -import org.keycloak.storage.ImportRealmFromRepresentationEvent; -import org.keycloak.storage.PartialImportRealmFromRepresentationEvent; -import org.keycloak.storage.SearchableModelField; -import org.keycloak.storage.SetDefaultsForNewRealm; -import org.keycloak.userprofile.UserProfileProvider; -import org.keycloak.util.JsonSerialization; -import org.keycloak.utils.ReservedCharValidator; -import org.keycloak.validation.ValidationUtil; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; -import static org.keycloak.models.utils.DefaultRequiredActions.getDefaultRequiredActionCaseInsensitively; -import static org.keycloak.models.utils.RepresentationToModel.createCredentials; -import static org.keycloak.models.utils.RepresentationToModel.createFederatedIdentities; -import static org.keycloak.models.utils.RepresentationToModel.createGroups; -import static org.keycloak.models.utils.RepresentationToModel.createRoleMappings; -import static org.keycloak.models.utils.RepresentationToModel.importGroup; -import static org.keycloak.models.utils.RepresentationToModel.importRoles; - -/** - * This wraps the functionality about export/import for legacy storage. - * - *

- * Currently, this only removes the user-storage and federation code from LegacyExportImportManager. - *

- * In the future, this needs to be rewritten completely. - * - * @author Alexander Schwartz - * @author Hynek Mlnarik - */ -public class MapExportImportManager implements ExportImportManager { - private final KeycloakSession session; - private static final Logger logger = Logger.getLogger(MapExportImportManager.class); - - /** - * Use the old import via the logical layer vs. the new method importing to CHM first and then copying over - * This is a temporary to test the functionality with the old representations until the new file store arrives in main, - * and will then be removed. - */ - private final boolean useNewImportMethod; - - public MapExportImportManager(KeycloakSession session) { - this.session = session; - useNewImportMethod = Boolean.parseBoolean(System.getProperty(MapExportImportManager.class.getName(), "false")); - } - - public MapExportImportManager(KeycloakSession session, boolean useNewImportMethod) { - this.session = session; - this.useNewImportMethod = useNewImportMethod; - } - - @Override - public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) { - convertDeprecatedSocialProviders(rep); - convertDeprecatedApplications(session, rep); - convertDeprecatedClientTemplates(rep); - - newRealm.setName(rep.getRealm()); - if (rep.getDisplayName() != null) newRealm.setDisplayName(rep.getDisplayName()); - if (rep.getDisplayNameHtml() != null) newRealm.setDisplayNameHtml(rep.getDisplayNameHtml()); - if (rep.isEnabled() != null) newRealm.setEnabled(rep.isEnabled()); - if (rep.isUserManagedAccessAllowed() != null) newRealm.setUserManagedAccessAllowed(rep.isUserManagedAccessAllowed()); - if (rep.isBruteForceProtected() != null) newRealm.setBruteForceProtected(rep.isBruteForceProtected()); - if (rep.isPermanentLockout() != null) newRealm.setPermanentLockout(rep.isPermanentLockout()); - if (rep.getMaxFailureWaitSeconds() != null) newRealm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds()); - if (rep.getMinimumQuickLoginWaitSeconds() != null) - newRealm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds()); - if (rep.getWaitIncrementSeconds() != null) newRealm.setWaitIncrementSeconds(rep.getWaitIncrementSeconds()); - if (rep.getQuickLoginCheckMilliSeconds() != null) - newRealm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); - if (rep.getMaxDeltaTimeSeconds() != null) newRealm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); - if (rep.getFailureFactor() != null) newRealm.setFailureFactor(rep.getFailureFactor()); - if (rep.isEventsEnabled() != null) newRealm.setEventsEnabled(rep.isEventsEnabled()); - if (rep.getEnabledEventTypes() != null) - newRealm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes())); - if (rep.getEventsExpiration() != null) newRealm.setEventsExpiration(rep.getEventsExpiration()); - if (rep.getEventsListeners() != null) newRealm.setEventsListeners(new HashSet<>(rep.getEventsListeners())); - if (rep.isAdminEventsEnabled() != null) newRealm.setAdminEventsEnabled(rep.isAdminEventsEnabled()); - if (rep.isAdminEventsDetailsEnabled() != null) - newRealm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled()); - - if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore()); - - if (rep.getDefaultSignatureAlgorithm() != null) newRealm.setDefaultSignatureAlgorithm(rep.getDefaultSignatureAlgorithm()); - else newRealm.setDefaultSignatureAlgorithm(Constants.DEFAULT_SIGNATURE_ALGORITHM); - - if (rep.getRevokeRefreshToken() != null) newRealm.setRevokeRefreshToken(rep.getRevokeRefreshToken()); - else newRealm.setRevokeRefreshToken(false); - - if (rep.getRefreshTokenMaxReuse() != null) newRealm.setRefreshTokenMaxReuse(rep.getRefreshTokenMaxReuse()); - else newRealm.setRefreshTokenMaxReuse(0); - - if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); - else newRealm.setAccessTokenLifespan(300); - - if (rep.getAccessTokenLifespanForImplicitFlow() != null) - newRealm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow()); - else - newRealm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT); - - if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); - else newRealm.setSsoSessionIdleTimeout(1800); - if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); - else newRealm.setSsoSessionMaxLifespan(36000); - if (rep.getSsoSessionMaxLifespanRememberMe() != null) newRealm.setSsoSessionMaxLifespanRememberMe(rep.getSsoSessionMaxLifespanRememberMe()); - if (rep.getSsoSessionIdleTimeoutRememberMe() != null) newRealm.setSsoSessionIdleTimeoutRememberMe(rep.getSsoSessionIdleTimeoutRememberMe()); - if (rep.getOfflineSessionIdleTimeout() != null) - newRealm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout()); - else newRealm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT); - - // KEYCLOAK-7688 Offline Session Max for Offline Token - if (rep.getOfflineSessionMaxLifespanEnabled() != null) newRealm.setOfflineSessionMaxLifespanEnabled(rep.getOfflineSessionMaxLifespanEnabled()); - else newRealm.setOfflineSessionMaxLifespanEnabled(false); - - if (rep.getOfflineSessionMaxLifespan() != null) - newRealm.setOfflineSessionMaxLifespan(rep.getOfflineSessionMaxLifespan()); - else newRealm.setOfflineSessionMaxLifespan(Constants.DEFAULT_OFFLINE_SESSION_MAX_LIFESPAN); - - if (rep.getClientSessionIdleTimeout() != null) - newRealm.setClientSessionIdleTimeout(rep.getClientSessionIdleTimeout()); - if (rep.getClientSessionMaxLifespan() != null) - newRealm.setClientSessionMaxLifespan(rep.getClientSessionMaxLifespan()); - - if (rep.getClientOfflineSessionIdleTimeout() != null) - newRealm.setClientOfflineSessionIdleTimeout(rep.getClientOfflineSessionIdleTimeout()); - if (rep.getClientOfflineSessionMaxLifespan() != null) - newRealm.setClientOfflineSessionMaxLifespan(rep.getClientOfflineSessionMaxLifespan()); - - if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); - else newRealm.setAccessCodeLifespan(60); - - if (rep.getAccessCodeLifespanUserAction() != null) - newRealm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); - else newRealm.setAccessCodeLifespanUserAction(300); - - if (rep.getAccessCodeLifespanLogin() != null) - newRealm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin()); - else newRealm.setAccessCodeLifespanLogin(1800); - - if (rep.getActionTokenGeneratedByAdminLifespan() != null) - newRealm.setActionTokenGeneratedByAdminLifespan(rep.getActionTokenGeneratedByAdminLifespan()); - else newRealm.setActionTokenGeneratedByAdminLifespan(12 * 60 * 60); - - if (rep.getActionTokenGeneratedByUserLifespan() != null) - newRealm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan()); - else newRealm.setActionTokenGeneratedByUserLifespan(newRealm.getAccessCodeLifespanUserAction()); - - // OAuth 2.0 Device Authorization Grant - OAuth2DeviceConfig deviceConfig = newRealm.getOAuth2DeviceConfig(); - - deviceConfig.setOAuth2DeviceCodeLifespan(newRealm, rep.getOAuth2DeviceCodeLifespan()); - deviceConfig.setOAuth2DevicePollingInterval(newRealm, rep.getOAuth2DevicePollingInterval()); - - if (rep.getSslRequired() != null) - newRealm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase())); - if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed()); - if (rep.isRegistrationEmailAsUsername() != null) - newRealm.setRegistrationEmailAsUsername(rep.isRegistrationEmailAsUsername()); - if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe()); - if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail()); - if (rep.isLoginWithEmailAllowed() != null) newRealm.setLoginWithEmailAllowed(rep.isLoginWithEmailAllowed()); - if (rep.isDuplicateEmailsAllowed() != null) newRealm.setDuplicateEmailsAllowed(rep.isDuplicateEmailsAllowed()); - if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); - if (rep.isEditUsernameAllowed() != null) newRealm.setEditUsernameAllowed(rep.isEditUsernameAllowed()); - if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme()); - if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme()); - if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme()); - if (rep.getEmailTheme() != null) newRealm.setEmailTheme(rep.getEmailTheme()); - if (rep.getLocalizationTexts() != null) { - Map> localizationTexts = rep.getLocalizationTexts(); - for (Map.Entry> entry: localizationTexts.entrySet()) { - newRealm.createOrUpdateRealmLocalizationTexts(entry.getKey(), entry.getValue()); - } - } - - // todo remove this stuff as its all deprecated - if (rep.getRequiredCredentials() != null) { - for (String requiredCred : rep.getRequiredCredentials()) { - newRealm.addRequiredCredential(requiredCred); - } - } else { - newRealm.addRequiredCredential(CredentialRepresentation.PASSWORD); - } - - if (rep.getPasswordPolicy() != null) - newRealm.setPasswordPolicy(PasswordPolicy.parse(session, rep.getPasswordPolicy())); - if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep)); - else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY); - - WebAuthnPolicy webAuthnPolicy = getWebAuthnPolicyTwoFactor(rep); - newRealm.setWebAuthnPolicy(webAuthnPolicy); - - webAuthnPolicy = getWebAuthnPolicyPasswordless(rep); - newRealm.setWebAuthnPolicyPasswordless(webAuthnPolicy); - - updateCibaSettings(rep, newRealm); - - updateParSettings(rep, newRealm); - - Map mappedFlows = importAuthenticationFlows(session, newRealm, rep); - if (rep.getRequiredActions() != null) { - for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { - RequiredActionProviderModel model = toModel(action); - - MigrationUtils.updateOTPRequiredAction(model); - - newRealm.addRequiredActionProvider(model); - } - DefaultRequiredActions.addDeleteAccountAction(newRealm); - } else { - DefaultRequiredActions.addActions(newRealm); - } - - importIdentityProviders(rep, newRealm, session); - importIdentityProviderMappers(rep, newRealm); - - Map clientScopes = new HashMap<>(); - if (rep.getClientScopes() != null) { - clientScopes = createClientScopes(session, rep.getClientScopes(), newRealm); - } - if (rep.getDefaultDefaultClientScopes() != null) { - for (String clientScopeName : rep.getDefaultDefaultClientScopes()) { - ClientScopeModel clientScope = clientScopes.get(clientScopeName); - if (clientScope != null) { - newRealm.addDefaultClientScope(clientScope, true); - } else { - logger.warnf("Referenced client scope '%s' doesn't exist", clientScopeName); - } - } - } - if (rep.getDefaultOptionalClientScopes() != null) { - for (String clientScopeName : rep.getDefaultOptionalClientScopes()) { - ClientScopeModel clientScope = clientScopes.get(clientScopeName); - if (clientScope != null) { - newRealm.addDefaultClientScope(clientScope, false); - } else { - logger.warnf("Referenced client scope '%s' doesn't exist", clientScopeName); - } - } - } - - Map createdClients = new HashMap<>(); - if (rep.getClients() != null) { - createdClients = createClients(session, rep, newRealm, mappedFlows); - } - - importRoles(rep.getRoles(), newRealm); - convertDeprecatedDefaultRoles(rep, newRealm); - - // Now that all possible roles and clients are created, create scope mappings - - if (rep.getClientScopeMappings() != null) { - - for (Map.Entry> entry : rep.getClientScopeMappings().entrySet()) { - ClientModel app = createdClients.computeIfAbsent(entry.getKey(), k -> newRealm.getClientByClientId(entry.getKey())); - if (app == null) { - throw new RuntimeException("Unable to find client role mappings for client: " + entry.getKey()); - } - createClientScopeMappings(newRealm, app, entry.getValue()); - } - } - - if (rep.getScopeMappings() != null) { - Map roleModelMap = newRealm.getRolesStream().collect(Collectors.toMap(RoleModel::getId, Function.identity())); - - for (ScopeMappingRepresentation scope : rep.getScopeMappings()) { - ScopeContainerModel scopeContainer = getScopeContainerHavingScope(newRealm, scope); - for (String roleString : scope.getRoles()) { - final String roleStringTrimmed = roleString.trim(); - RoleModel role = roleModelMap.computeIfAbsent(roleStringTrimmed, k -> newRealm.getRole(roleStringTrimmed)); - if (role == null) { - role = newRealm.addRole(roleString); - roleModelMap.put(role.getId(), role); - } - scopeContainer.addScopeMapping(role); - } - } - } - - if (rep.getSmtpServer() != null) { - newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer())); - } - - if (rep.getBrowserSecurityHeaders() != null) { - newRealm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders()); - } else { - newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.realmDefaultHeaders); - } - - if (rep.getComponents() != null) { - MultivaluedHashMap components = rep.getComponents(); - String parentId = newRealm.getId(); - importComponents(newRealm, components, parentId); - } - - if (rep.getGroups() != null) { - importGroups(newRealm, rep); - if (rep.getDefaultGroups() != null) { - KeycloakModelUtils.setDefaultGroups(session, newRealm, rep.getDefaultGroups().stream()); - } - } - - - // create users and their role mappings and social mappings - - if (rep.getUsers() != null) { - for (UserRepresentation userRep : rep.getUsers()) { - createUser(newRealm, userRep); - } - } - - if (!skipUserDependent) { - importRealmAuthorizationSettings(rep, newRealm, session); - } - - if (rep.isInternationalizationEnabled() != null) { - newRealm.setInternationalizationEnabled(rep.isInternationalizationEnabled()); - } - if (rep.getSupportedLocales() != null) { - newRealm.setSupportedLocales(new HashSet<>(rep.getSupportedLocales())); - } - if (rep.getDefaultLocale() != null) { - newRealm.setDefaultLocale(rep.getDefaultLocale()); - } - - // import attributes - - if (rep.getAttributes() != null) { - for (Map.Entry attr : rep.getAttributes().entrySet()) { - newRealm.setAttribute(attr.getKey(), attr.getValue()); - } - } - - if (!useNewImportMethod) { - if (newRealm.getComponentsStream(newRealm.getId(), KeyProvider.class.getName()).count() == 0) { - if (rep.getPrivateKey() != null) { - DefaultKeyProviders.createProviders(newRealm, rep.getPrivateKey(), rep.getCertificate()); - } else { - DefaultKeyProviders.createProviders(newRealm); - } - } - } else { - if (rep.getPrivateKey() != null) { - - ComponentModel rsa = new ComponentModel(); - rsa.setName("rsa"); - rsa.setParentId(newRealm.getId()); - rsa.setProviderId("rsa"); - rsa.setProviderType(KeyProvider.class.getName()); - - MultivaluedHashMap config = new MultivaluedHashMap<>(); - config.putSingle("priority", "100"); - config.putSingle("privateKey", rep.getPrivateKey()); - if (rep.getCertificate() != null) { - config.putSingle("certificate", rep.getCertificate()); - } - rsa.setConfig(config); - - newRealm.addComponentModel(rsa); - } - } - } - - private static RoleModel getOrAddRealmRole(RealmModel realm, String name) { - RoleModel role = realm.getRole(name); - if (role == null) { - role = realm.addRole(name); - } - return role; - } - - @Override - public void exportRealm(RealmModel realm, ExportOptions options, ExportAdapter callback) { - throw new ModelException("exporting for map storage is currently not supported"); - } - - @Override - public RealmModel importRealm(InputStream requestBody) { - /* A future implementation that would differentiate between the old JSON representations and the new file store - might want to add the file name or the media type as a method parameter to switch between different implementations. */ - - RealmRepresentation rep; - byte[] inputData = null; - try { - // read input data to be able to re-try later - try (requestBody) { - inputData = requestBody.readAllBytes(); - } - rep = JsonSerialization.readValue(new ByteArrayInputStream(inputData), RealmRepresentation.class); - } catch (IOException e) { - /* This is a re-try when unrecognized property is being imported, it may happen e.g. when using admin client of newer version - in heterogenous cluster (during zero-downtime upgrade) and the request lands into older version of kc. */ - if (e instanceof UnrecognizedPropertyException && inputData != null) { - try { - rep = JsonSerialization.mapper.copy().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).readValue(new ByteArrayInputStream(inputData), RealmRepresentation.class); - logger.warnf("%s during an import!", e.getMessage().indexOf(",") > 0 ? e.getMessage().substring(0, e.getMessage().indexOf(",")) : "Unrecognized field"); - } catch (IOException ex) { - throw new ModelException("unable to read contents from stream", ex); - } - } else { - throw new ModelException("unable to read contents from stream", e); - } - } - - logger.debugv("importRealm: {0}", rep.getRealm()); - - if (!useNewImportMethod) { - /* The import for the JSON representation might be called from the Admin UI, where it will be empty except for - the realm name and if the realm is enabled. For that scenario, it would need to create all missing elements, - which is done by firing an event to call the existing implementation in the RealmManager. */ - return ImportRealmFromRepresentationEvent.fire(session, rep); - } else { - /* This makes use of the representation to mimic the future setup: Some kind of import into a ConcurrentHashMap in-memory and then copying - that over to the real store. This is the basis for future file store import. Results are different - when importing, for example, an empty list of roles vs a non-existing list of roles, and possibility in other ways. - Importing from a classic representation will eventually be removed and replaced when the new file store arrived. */ - return importToChmAndThenCopyOver(rep); - } - } - - - @Override - public PartialImportResults partialImportRealm(RealmModel realm, InputStream requestBody) { - /* A future implementation that would differentiate between the old JSON representations and the new file store - might want to add the file name or the media type as a method parameter to switch between different implementations. */ - - PartialImportRepresentation rep; - try { - rep = JsonSerialization.readValue(requestBody, PartialImportRepresentation.class); - } catch (IOException e) { - throw new ModelException("unable to read contents from stream", e); - } - - /* The import for the legacy JSON representation might be called from the Admin UI, and it allows for several options as part - * of the representation. Therefore, direct this to the service layer with a (temporary) event so that the logic isn't duplicated - * between legacy and map store. - */ - return PartialImportRealmFromRepresentationEvent.fire(session, rep, realm); - } - - private RealmModel importToChmAndThenCopyOver(RealmRepresentation rep) { - String id = rep.getId(); - if (id == null || id.trim().isEmpty()) { - id = KeycloakModelUtils.generateId(); - } else { - ReservedCharValidator.validate(id); - } - - ReservedCharValidator.validate(rep.getRealm()); - - RealmModel realm; - RealmModel currentRealm = session.getContext().getRealm(); - - try { - - String _id = id; - KeycloakModelUtils.runJobInTransaction(new ImportSessionFactoryWrapper(session.getKeycloakSessionFactory()), chmSession -> { - // import the representation - fillRealm(chmSession, _id, rep); - - // copy over the realm from in-memory to the real - copyRealm(_id, chmSession); - copyEntities(_id, chmSession, ClientProvider.class, ClientModel.class, ClientModel.SearchableFields.REALM_ID); - copyEntities(_id, chmSession, ClientScopeProvider.class, ClientScopeModel.class, ClientScopeModel.SearchableFields.REALM_ID); - copyEntities(_id, chmSession, GroupProvider.class, GroupModel.class, GroupModel.SearchableFields.REALM_ID); - copyEntities(_id, chmSession, UserProvider.class, UserModel.class, UserModel.SearchableFields.REALM_ID); - copyEntities(_id, chmSession, RoleProvider.class, RoleModel.class, RoleModel.SearchableFields.REALM_ID); - - // clear the CHM store - chmSession.getTransactionManager().setRollbackOnly(); - }); - - realm = session.realms().getRealm(id); - session.getContext().setRealm(realm); - setupMasterAdminManagement(realm); - ImpersonationConstants.setupImpersonationService(session, realm); - fireRealmPostCreate(realm); - } finally { - session.getContext().setRealm(currentRealm); - } - - return realm; - } - - private void copyRealm(String realmId, KeycloakSession sessionChm) { - MapRealmEntity realmEntityChm = (MapRealmEntity) getMapStorage(sessionChm, RealmProvider.class).read(realmId); - getMapStorage(session, RealmProvider.class).create(realmEntityChm); - } - - private static

MapStorage getMapStorage(KeycloakSession session, Class

provider) { - ProviderFactory

factoryChm = session.getKeycloakSessionFactory().getProviderFactory(provider); - return ((AbstractMapProviderFactory) factoryChm).getMapStorage(session); - } - - private

void copyEntities(String realmId, KeycloakSession sessionChm, Class

provider, Class model, SearchableModelField field) { - MapStorage storeChm = getMapStorage(sessionChm, provider); - MapStorage storeOrig = getMapStorage(session, provider); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(field, ModelCriteriaBuilder.Operator.EQ, realmId); - - storeChm.read(withCriteria(mcb)).forEach(storeOrig::create); - } - - private static void fillRealm(KeycloakSession session, String id, RealmRepresentation rep) { - RealmModel realm = session.realms().createRealm(id, rep.getRealm()); - session.getContext().setRealm(realm); - SetDefaultsForNewRealm.fire(session, realm); - MapExportImportManager mapExportImportManager = new MapExportImportManager(session); - mapExportImportManager.clearDefaultsThatConflictWithRepresentation(rep, realm); - mapExportImportManager.importRealm(rep, realm, false); - } - - private void clearDefaultsThatConflictWithRepresentation(RealmRepresentation rep, RealmModel newRealm) { - if (rep.getDefaultRole() != null) { - if (newRealm.getDefaultRole() != null) { - newRealm.removeRole(newRealm.getDefaultRole()); - // set the new role here already as the legacy code expects it this way - newRealm.setDefaultRole(RepresentationToModel.createRole(newRealm, rep.getDefaultRole())); - } - } - - if (rep.getRequiredActions() != null) { - for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { - RequiredActionProviderModel requiredActionProviderByAlias = newRealm.getRequiredActionProviderByAlias(action.getAlias()); - if (requiredActionProviderByAlias != null) { - newRealm.removeRequiredActionProvider(requiredActionProviderByAlias); - } - } - } - - if (rep.getRoles() != null) { - for (RoleRepresentation representation : rep.getRoles().getRealm()) { - RoleModel role = newRealm.getRole(representation.getName()); - if (role != null && (newRealm.getDefaultRole() == null || newRealm.getDefaultRole() != null && !Objects.equals(role.getId(), newRealm.getDefaultRole().getId()))) { - newRealm.removeRole(role); - } - } - } - - if (rep.getPrivateKey() != null) { - newRealm.getComponentsStream(newRealm.getId(), KeyProvider.class.getName()) - .filter(component -> Objects.equals(component.getProviderId(), "rsa-generated") || Objects.equals(component.getProviderId(), "rsa-enc-generated")) - .collect(Collectors.toList()).forEach(newRealm::removeComponent); - // will later create the "rsa" provider - } - - if (rep.getClients() != null) { - for (ClientRepresentation resourceRep : rep.getClients()) { - ClientModel clientByClientId = newRealm.getClientByClientId(resourceRep.getClientId()); - if (clientByClientId != null) { - newRealm.removeClient(clientByClientId.getId()); - } - } - } - - if (rep.getClientScopes() != null) { - for (ClientScopeRepresentation resourceRep : rep.getClientScopes()) { - Optional existingClientScope = newRealm.getClientScopesStream().filter(clientScopeModel -> clientScopeModel.getName().equals(resourceRep.getName())).findFirst(); - if (existingClientScope.isPresent()) { - newRealm.removeClientScope(existingClientScope.get().getId()); - } - } - } - - if (rep.getComponents() != null) { - clearExistingComponents(newRealm, rep.getComponents()); - } - } - - protected static void clearExistingComponents(RealmModel newRealm, MultivaluedHashMap components) { - for (Map.Entry> entry : components.entrySet()) { - String providerType = entry.getKey(); - for (ComponentExportRepresentation compRep : entry.getValue()) { - newRealm.getComponentsStream(newRealm.getId(), providerType) - .filter(component -> Objects.equals(component.getProviderId(), compRep.getProviderId())).findAny().ifPresent(newRealm::removeComponent); - if (compRep.getSubComponents() != null) { - clearExistingComponents(newRealm, compRep.getSubComponents()); - } - } - } - } - - public void setupMasterAdminManagement(RealmModel realm) { - // Need to refresh masterApp for current realm - String adminRealmName = Config.getAdminRealm(); - RealmModel adminRealm = session.realms().getRealmByName(adminRealmName); - ClientModel masterApp = adminRealm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName())); - if (masterApp == null) { - createMasterAdminManagement(realm); - return; - } - realm.setMasterAdminClient(masterApp); - } - - private void createMasterAdminManagement(RealmModel realm) { - RealmModel adminRealm; - RoleModel adminRole; - - if (realm.getName().equals(Config.getAdminRealm())) { - adminRealm = realm; - - adminRole = realm.addRole(AdminRoles.ADMIN); - - RoleModel createRealmRole = realm.addRole(AdminRoles.CREATE_REALM); - adminRole.addCompositeRole(createRealmRole); - createRealmRole.setDescription("${role_" + AdminRoles.CREATE_REALM + "}"); - } else { - adminRealm = session.realms().getRealmByName(Config.getAdminRealm()); - adminRole = adminRealm.getRole(AdminRoles.ADMIN); - } - adminRole.setDescription("${role_" + AdminRoles.ADMIN + "}"); - - ClientModel realmAdminApp = KeycloakModelUtils.createManagementClient(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName())); - // No localized name for now - realmAdminApp.setName(realm.getName() + " Realm"); - realm.setMasterAdminClient(realmAdminApp); - - for (String r : AdminRoles.ALL_REALM_ROLES) { - RoleModel role = realmAdminApp.addRole(r); - role.setDescription("${role_" + r + "}"); - adminRole.addCompositeRole(role); - } - addQueryCompositeRoles(realmAdminApp); - } - - public void addQueryCompositeRoles(ClientModel realmAccess) { - RoleModel queryClients = realmAccess.getRole(AdminRoles.QUERY_CLIENTS); - RoleModel queryUsers = realmAccess.getRole(AdminRoles.QUERY_USERS); - RoleModel queryGroups = realmAccess.getRole(AdminRoles.QUERY_GROUPS); - - RoleModel viewClients = realmAccess.getRole(AdminRoles.VIEW_CLIENTS); - viewClients.addCompositeRole(queryClients); - RoleModel viewUsers = realmAccess.getRole(AdminRoles.VIEW_USERS); - viewUsers.addCompositeRole(queryUsers); - viewUsers.addCompositeRole(queryGroups); - } - - private void fireRealmPostCreate(RealmModel realm) { - session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() { - @Override - public RealmModel getCreatedRealm() { - return realm; - } - @Override - public KeycloakSession getKeycloakSession() { - return session; - } - }); - - } - - private static void convertDeprecatedDefaultRoles(RealmRepresentation rep, RealmModel newRealm) { - if (rep.getDefaultRole() == null) { - - // Setup realm default roles - if (rep.getDefaultRoles() != null) { - rep.getDefaultRoles().stream() - .map(String::trim) - .map(name -> getOrAddRealmRole(newRealm, name)) - .forEach(role -> newRealm.getDefaultRole().addCompositeRole(role)); - } - - // Setup client default roles - if (rep.getClients() != null) { - for (ClientRepresentation clientRep : rep.getClients()) { - if (clientRep.getDefaultRoles() != null) { - Arrays.stream(clientRep.getDefaultRoles()) - .map(String::trim) - .map(name -> getOrAddClientRole(newRealm.getClientById(clientRep.getId()), name)) - .forEach(role -> newRealm.getDefaultRole().addCompositeRole(role)); - } - } - } - } - } - - private static RoleModel getOrAddClientRole(ClientModel client, String name) { - RoleModel role = client.getRole(name); - if (role == null) { - role = client.addRole(name); - } - return role; - } - - private static Map createClients(KeycloakSession session, RealmRepresentation rep, RealmModel realm, Map mappedFlows) { - Map appMap = new HashMap<>(); - for (ClientRepresentation resourceRep : rep.getClients()) { - ClientModel app = RepresentationToModel.createClient(session, realm, resourceRep, mappedFlows); - String postLogoutRedirectUris = app.getAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS); - if (postLogoutRedirectUris == null) { - app.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+"); - } - appMap.put(app.getClientId(), app); - - ValidationUtil.validateClient(session, app, false, r -> { - throw new RuntimeException("Invalid client " + app.getClientId() + ": " + r.getAllErrorsAsString()); - }); - } - return appMap; - } - - private static Map createClientScopes(KeycloakSession session, List clientScopes, RealmModel realm) { - Map appMap = new HashMap<>(); - for (ClientScopeRepresentation resourceRep : clientScopes) { - ClientScopeModel app = RepresentationToModel.createClientScope(session, realm, resourceRep); - appMap.put(app.getName(), app); - } - return appMap; - } - - private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm, KeycloakSession session) { - if (rep.getIdentityProviders() != null) { - for (IdentityProviderRepresentation representation : rep.getIdentityProviders()) { - newRealm.addIdentityProvider(RepresentationToModel.toModel(newRealm, representation, session)); - } - } - } - - private static void importIdentityProviderMappers(RealmRepresentation rep, RealmModel newRealm) { - if (rep.getIdentityProviderMappers() != null) { - for (IdentityProviderMapperRepresentation representation : rep.getIdentityProviderMappers()) { - newRealm.addIdentityProviderMapper(RepresentationToModel.toModel(representation)); - } - } - } - - public static long getClaimsMask(ClaimRepresentation rep) { - long mask = ClaimMask.ALL; - - if (rep.getAddress()) { - mask |= ClaimMask.ADDRESS; - } else { - mask &= ~ClaimMask.ADDRESS; - } - if (rep.getEmail()) { - mask |= ClaimMask.EMAIL; - } else { - mask &= ~ClaimMask.EMAIL; - } - if (rep.getGender()) { - mask |= ClaimMask.GENDER; - } else { - mask &= ~ClaimMask.GENDER; - } - if (rep.getLocale()) { - mask |= ClaimMask.LOCALE; - } else { - mask &= ~ClaimMask.LOCALE; - } - if (rep.getName()) { - mask |= ClaimMask.NAME; - } else { - mask &= ~ClaimMask.NAME; - } - if (rep.getPhone()) { - mask |= ClaimMask.PHONE; - } else { - mask &= ~ClaimMask.PHONE; - } - if (rep.getPicture()) { - mask |= ClaimMask.PICTURE; - } else { - mask &= ~ClaimMask.PICTURE; - } - if (rep.getProfile()) { - mask |= ClaimMask.PROFILE; - } else { - mask &= ~ClaimMask.PROFILE; - } - if (rep.getUsername()) { - mask |= ClaimMask.USERNAME; - } else { - mask &= ~ClaimMask.USERNAME; - } - if (rep.getWebsite()) { - mask |= ClaimMask.WEBSITE; - } else { - mask &= ~ClaimMask.WEBSITE; - } - return mask; - } - - public static void createClientScopeMappings(RealmModel realm, ClientModel clientModel, List mappings) { - for (ScopeMappingRepresentation mapping : mappings) { - ScopeContainerModel scopeContainer = getScopeContainerHavingScope(realm, mapping); - - for (String roleString : mapping.getRoles()) { - RoleModel role = clientModel.getRole(roleString.trim()); - if (role == null) { - role = clientModel.addRole(roleString.trim()); - } - scopeContainer.addScopeMapping(role); - } - } - } - - private static ScopeContainerModel getScopeContainerHavingScope(RealmModel realm, ScopeMappingRepresentation scope) { - if (scope.getClient() != null) { - ClientModel client = realm.getClientByClientId(scope.getClient()); - if (client == null) { - throw new RuntimeException("Unknown client specification in scope mappings: " + scope.getClient()); - } - return client; - } else if (scope.getClientScope() != null) { - ClientScopeModel clientScope = KeycloakModelUtils.getClientScopeByName(realm, scope.getClientScope()); - if (clientScope == null) { - throw new RuntimeException("Unknown clientScope specification in scope mappings: " + scope.getClientScope()); - } - return clientScope; - } else if (scope.getClientTemplate() != null) { // Backwards compatibility - String templateName = KeycloakModelUtils.convertClientScopeName(scope.getClientTemplate()); - ClientScopeModel clientTemplate = KeycloakModelUtils.getClientScopeByName(realm, templateName); - if (clientTemplate == null) { - throw new RuntimeException("Unknown clientScope specification in scope mappings: " + templateName); - } - return clientTemplate; - } else { - throw new RuntimeException("Either client or clientScope needs to be specified in scope mappings"); - } - } - - - public static void renameRealm(RealmModel realm, String name) { - if (name.equals(realm.getName())) return; - - String oldName = realm.getName(); - - ClientModel masterApp = realm.getMasterAdminClient(); - masterApp.setClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(name)); - realm.setName(name); - - ClientModel adminClient = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID); - if (adminClient != null) { - if (adminClient.getBaseUrl() != null) { - adminClient.setBaseUrl(adminClient.getBaseUrl().replace("/admin/" + oldName + "/", "/admin/" + name + "/")); - } - Set adminRedirectUris = new HashSet<>(); - for (String r : adminClient.getRedirectUris()) { - adminRedirectUris.add(replace(r, "/admin/" + oldName + "/", "/admin/" + name + "/")); - } - adminClient.setRedirectUris(adminRedirectUris); - } - - ClientModel accountClient = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); - if (accountClient != null) { - if (accountClient.getBaseUrl() != null) { - accountClient.setBaseUrl(accountClient.getBaseUrl().replace("/realms/" + oldName + "/", "/realms/" + name + "/")); - } - Set accountRedirectUris = new HashSet<>(); - for (String r : accountClient.getRedirectUris()) { - accountRedirectUris.add(replace(r, "/realms/" + oldName + "/", "/realms/" + name + "/")); - } - accountClient.setRedirectUris(accountRedirectUris); - } - - ClientModel accountConsoleClient = realm.getClientByClientId(Constants.ACCOUNT_CONSOLE_CLIENT_ID); - if (accountConsoleClient != null) { - if (accountConsoleClient.getBaseUrl() != null) { - accountConsoleClient.setBaseUrl(accountConsoleClient.getBaseUrl().replace("/realms/" + oldName + "/", "/realms/" + name + "/")); - } - Set accountConsoleRedirectUris = new HashSet<>(); - for (String r : accountConsoleClient.getRedirectUris()) { - accountConsoleRedirectUris.add(replace(r, "/realms/" + oldName + "/", "/realms/" + name + "/")); - } - accountConsoleClient.setRedirectUris(accountConsoleRedirectUris); - } - } - - private static String replace(String url, String target, String replacement) { - return url != null ? url.replace(target, replacement) : null; - } - - @Override - public void updateRealm(RealmRepresentation rep, RealmModel realm) { - if (rep.getRealm() != null) { - renameRealm(realm, rep.getRealm()); - } - - // Import attributes first, so the stuff saved directly on representation (displayName, bruteForce etc) has bigger priority - if (rep.getAttributes() != null) { - Set attrsToRemove = new HashSet<>(realm.getAttributes().keySet()); - attrsToRemove.removeAll(rep.getAttributes().keySet()); - - for (Map.Entry entry : rep.getAttributes().entrySet()) { - realm.setAttribute(entry.getKey(), entry.getValue()); - } - - for (String attr : attrsToRemove) { - realm.removeAttribute(attr); - } - } - - if (rep.getDefaultGroups() != null) { - KeycloakModelUtils.setDefaultGroups(session, realm, rep.getDefaultGroups().stream()); - } - if (rep.getDisplayName() != null) realm.setDisplayName(rep.getDisplayName()); - if (rep.getDisplayNameHtml() != null) realm.setDisplayNameHtml(rep.getDisplayNameHtml()); - if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled()); - if (rep.isUserManagedAccessAllowed() != null) realm.setUserManagedAccessAllowed(rep.isUserManagedAccessAllowed()); - if (rep.isBruteForceProtected() != null) realm.setBruteForceProtected(rep.isBruteForceProtected()); - if (rep.isPermanentLockout() != null) realm.setPermanentLockout(rep.isPermanentLockout()); - if (rep.getMaxFailureWaitSeconds() != null) realm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds()); - if (rep.getMinimumQuickLoginWaitSeconds() != null) - realm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds()); - if (rep.getWaitIncrementSeconds() != null) realm.setWaitIncrementSeconds(rep.getWaitIncrementSeconds()); - if (rep.getQuickLoginCheckMilliSeconds() != null) - realm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); - if (rep.getMaxDeltaTimeSeconds() != null) realm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); - if (rep.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor()); - if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed()); - if (rep.isRegistrationEmailAsUsername() != null) - realm.setRegistrationEmailAsUsername(rep.isRegistrationEmailAsUsername()); - if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe()); - if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail()); - if (rep.isLoginWithEmailAllowed() != null) realm.setLoginWithEmailAllowed(rep.isLoginWithEmailAllowed()); - if (rep.isDuplicateEmailsAllowed() != null) realm.setDuplicateEmailsAllowed(rep.isDuplicateEmailsAllowed()); - if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); - if (rep.isEditUsernameAllowed() != null) realm.setEditUsernameAllowed(rep.isEditUsernameAllowed()); - if (rep.getSslRequired() != null) realm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase())); - if (rep.getAccessCodeLifespan() != null) realm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); - if (rep.getAccessCodeLifespanUserAction() != null) - realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); - if (rep.getAccessCodeLifespanLogin() != null) - realm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin()); - if (rep.getActionTokenGeneratedByAdminLifespan() != null) - realm.setActionTokenGeneratedByAdminLifespan(rep.getActionTokenGeneratedByAdminLifespan()); - if (rep.getActionTokenGeneratedByUserLifespan() != null) - realm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan()); - - OAuth2DeviceConfig deviceConfig = realm.getOAuth2DeviceConfig(); - - deviceConfig.setOAuth2DeviceCodeLifespan(realm, rep.getOAuth2DeviceCodeLifespan()); - deviceConfig.setOAuth2DevicePollingInterval(realm, rep.getOAuth2DevicePollingInterval()); - - if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore()); - if (rep.getDefaultSignatureAlgorithm() != null) realm.setDefaultSignatureAlgorithm(rep.getDefaultSignatureAlgorithm()); - if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken()); - if (rep.getRefreshTokenMaxReuse() != null) realm.setRefreshTokenMaxReuse(rep.getRefreshTokenMaxReuse()); - if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); - if (rep.getAccessTokenLifespanForImplicitFlow() != null) - realm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow()); - if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); - if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); - if (rep.getSsoSessionIdleTimeoutRememberMe() != null) realm.setSsoSessionIdleTimeoutRememberMe(rep.getSsoSessionIdleTimeoutRememberMe()); - if (rep.getSsoSessionMaxLifespanRememberMe() != null) realm.setSsoSessionMaxLifespanRememberMe(rep.getSsoSessionMaxLifespanRememberMe()); - if (rep.getOfflineSessionIdleTimeout() != null) - realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout()); - // KEYCLOAK-7688 Offline Session Max for Offline Token - if (rep.getOfflineSessionMaxLifespanEnabled() != null) realm.setOfflineSessionMaxLifespanEnabled(rep.getOfflineSessionMaxLifespanEnabled()); - if (rep.getOfflineSessionMaxLifespan() != null) - realm.setOfflineSessionMaxLifespan(rep.getOfflineSessionMaxLifespan()); - if (rep.getClientSessionIdleTimeout() != null) - realm.setClientSessionIdleTimeout(rep.getClientSessionIdleTimeout()); - if (rep.getClientSessionMaxLifespan() != null) - realm.setClientSessionMaxLifespan(rep.getClientSessionMaxLifespan()); - if (rep.getClientOfflineSessionIdleTimeout() != null) - realm.setClientOfflineSessionIdleTimeout(rep.getClientOfflineSessionIdleTimeout()); - if (rep.getClientOfflineSessionMaxLifespan() != null) - realm.setClientOfflineSessionMaxLifespan(rep.getClientOfflineSessionMaxLifespan()); - if (rep.getRequiredCredentials() != null) { - realm.updateRequiredCredentials(rep.getRequiredCredentials()); - } - if (rep.getLoginTheme() != null) realm.setLoginTheme(rep.getLoginTheme()); - if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme()); - if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme()); - if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme()); - - if (rep.isEventsEnabled() != null) realm.setEventsEnabled(rep.isEventsEnabled()); - if (rep.getEventsExpiration() != null) realm.setEventsExpiration(rep.getEventsExpiration()); - if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<>(rep.getEventsListeners())); - if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes())); - - if (rep.isAdminEventsEnabled() != null) realm.setAdminEventsEnabled(rep.isAdminEventsEnabled()); - if (rep.isAdminEventsDetailsEnabled() != null) - realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled()); - - - if (rep.getPasswordPolicy() != null) - realm.setPasswordPolicy(PasswordPolicy.parse(session, rep.getPasswordPolicy())); - if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep)); - - WebAuthnPolicy webAuthnPolicy = getWebAuthnPolicyTwoFactor(rep); - realm.setWebAuthnPolicy(webAuthnPolicy); - - webAuthnPolicy = getWebAuthnPolicyPasswordless(rep); - realm.setWebAuthnPolicyPasswordless(webAuthnPolicy); - - updateCibaSettings(rep, realm); - updateParSettings(rep, realm); - session.clientPolicy().updateRealmModelFromRepresentation(realm, rep); - - if (rep.getSmtpServer() != null) { - Map config = new HashMap(rep.getSmtpServer()); - if (rep.getSmtpServer().containsKey("password") && ComponentRepresentation.SECRET_VALUE.equals(rep.getSmtpServer().get("password"))) { - String passwordValue = realm.getSmtpConfig() != null ? realm.getSmtpConfig().get("password") : null; - config.put("password", passwordValue); - } - realm.setSmtpConfig(config); - } - - if (rep.getBrowserSecurityHeaders() != null) { - realm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders()); - } - - if (rep.isInternationalizationEnabled() != null) { - realm.setInternationalizationEnabled(rep.isInternationalizationEnabled()); - } - if (rep.getSupportedLocales() != null) { - realm.setSupportedLocales(new HashSet<>(rep.getSupportedLocales())); - } - if (rep.getDefaultLocale() != null) { - realm.setDefaultLocale(rep.getDefaultLocale()); - } - if (rep.getBrowserFlow() != null) { - realm.setBrowserFlow(realm.getFlowByAlias(rep.getBrowserFlow())); - } - if (rep.getRegistrationFlow() != null) { - realm.setRegistrationFlow(realm.getFlowByAlias(rep.getRegistrationFlow())); - } - if (rep.getDirectGrantFlow() != null) { - realm.setDirectGrantFlow(realm.getFlowByAlias(rep.getDirectGrantFlow())); - } - if (rep.getResetCredentialsFlow() != null) { - realm.setResetCredentialsFlow(realm.getFlowByAlias(rep.getResetCredentialsFlow())); - } - if (rep.getClientAuthenticationFlow() != null) { - realm.setClientAuthenticationFlow(realm.getFlowByAlias(rep.getClientAuthenticationFlow())); - } - if (rep.getDockerAuthenticationFlow() != null) { - realm.setDockerAuthenticationFlow(realm.getFlowByAlias(rep.getDockerAuthenticationFlow())); - } - } - - @Override - public UserModel createUser(RealmModel newRealm, UserRepresentation userRep) { - convertDeprecatedSocialProviders(userRep); - - // Import users just to user storage. Don't federate - UserModel user = session.users().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false); - user.setEnabled(userRep.isEnabled() != null && userRep.isEnabled()); - user.setCreatedTimestamp(userRep.getCreatedTimestamp()); - user.setEmail(userRep.getEmail()); - if (userRep.isEmailVerified() != null) user.setEmailVerified(userRep.isEmailVerified()); - user.setFirstName(userRep.getFirstName()); - user.setLastName(userRep.getLastName()); - user.setFederationLink(userRep.getFederationLink()); - if (userRep.getAttributes() != null) { - for (Map.Entry> entry : userRep.getAttributes().entrySet()) { - List value = entry.getValue(); - if (value != null) { - user.setAttribute(entry.getKey(), new ArrayList<>(value)); - } - } - } - if (userRep.getRequiredActions() != null) { - for (String requiredAction : userRep.getRequiredActions()) { - user.addRequiredAction(getDefaultRequiredActionCaseInsensitively(requiredAction)); - } - } - createCredentials(userRep, session, newRealm, user, false); - createFederatedIdentities(userRep, session, newRealm, user); - createRoleMappings(userRep, user, newRealm); - if (userRep.getClientConsents() != null) { - for (UserConsentRepresentation consentRep : userRep.getClientConsents()) { - UserConsentModel consentModel = RepresentationToModel.toModel(newRealm, consentRep); - session.users().addConsent(newRealm, user.getId(), consentModel); - } - } - - if (userRep.getNotBefore() != null) { - session.users().setNotBeforeForUser(newRealm, user, userRep.getNotBefore()); - } - - if (userRep.getServiceAccountClientId() != null) { - String clientId = userRep.getServiceAccountClientId(); - ClientModel client = newRealm.getClientByClientId(clientId); - if (client == null) { - throw new RuntimeException("Unable to find client specified for service account link. Client: " + clientId); - } - user.setServiceAccountClientLink(client.getId()); - } - createGroups(session, userRep, newRealm, user); - return user; - } - - public static void convertDeprecatedSocialProviders(UserRepresentation user) { - if (user.getSocialLinks() != null && !user.getSocialLinks().isEmpty() && user.getFederatedIdentities() == null) { - logger.warnf("Using deprecated 'socialLinks' configuration in for user '%s' was removed", user.getUsername()); - } - - user.setSocialLinks(null); - } - - private static void convertDeprecatedApplications(KeycloakSession session, RealmRepresentation realm) { - if (realm.getApplications() != null || realm.getOauthClients() != null) { - if (realm.getClients() == null) { - realm.setClients(new LinkedList<>()); - } - - List clients = new LinkedList<>(); - if (realm.getApplications() != null) { - clients.addAll(realm.getApplications()); - } - if (realm.getOauthClients() != null) { - clients.addAll(realm.getOauthClients()); - } - - for (ApplicationRepresentation app : clients) { - app.setClientId(app.getName()); - app.setName(null); - - if (app instanceof OAuthClientRepresentation) { - app.setConsentRequired(true); - app.setFullScopeAllowed(false); - } - - if (app.getProtocolMappers() == null && app.getClaims() != null) { - long mask = getClaimsMask(app.getClaims()); - List convertedProtocolMappers = session.getProvider(MigrationProvider.class).getMappersForClaimMask(mask); - app.setProtocolMappers(convertedProtocolMappers); - app.setClaims(null); - } - - realm.getClients().add(app); - } - } - - if (realm.getApplicationScopeMappings() != null && realm.getClientScopeMappings() == null) { - realm.setClientScopeMappings(realm.getApplicationScopeMappings()); - } - - if (realm.getRoles() != null && realm.getRoles().getApplication() != null && realm.getRoles().getClient() == null) { - realm.getRoles().setClient(realm.getRoles().getApplication()); - } - - if (realm.getUsers() != null) { - for (UserRepresentation user : realm.getUsers()) { - if (user.getApplicationRoles() != null && user.getClientRoles() == null) { - user.setClientRoles(user.getApplicationRoles()); - } - } - } - - if (realm.getRoles() != null && realm.getRoles().getRealm() != null) { - for (RoleRepresentation role : realm.getRoles().getRealm()) { - if (role.getComposites() != null && role.getComposites().getApplication() != null && role.getComposites().getClient() == null) { - role.getComposites().setClient(role.getComposites().getApplication()); - } - } - } - - if (realm.getRoles() != null && realm.getRoles().getClient() != null) { - for (Map.Entry> clientRoles : realm.getRoles().getClient().entrySet()) { - for (RoleRepresentation role : clientRoles.getValue()) { - if (role.getComposites() != null && role.getComposites().getApplication() != null && role.getComposites().getClient() == null) { - role.getComposites().setClient(role.getComposites().getApplication()); - } - } - } - } - } - - private static void convertDeprecatedSocialProviders(RealmRepresentation rep) { - if (rep.isSocial() != null && rep.isSocial() && rep.getSocialProviders() != null && !rep.getSocialProviders().isEmpty() && rep.getIdentityProviders() == null) { - Boolean updateProfileFirstLogin = rep.isUpdateProfileOnInitialSocialLogin() != null && rep.isUpdateProfileOnInitialSocialLogin(); - if (rep.getSocialProviders() != null) { - - logger.warn("Using deprecated 'social' configuration in JSON representation. It will be removed in future versions"); - List identityProviders = new LinkedList<>(); - for (String k : rep.getSocialProviders().keySet()) { - if (k.endsWith(".key")) { - String providerId = k.split("\\.")[0]; - String key = rep.getSocialProviders().get(k); - String secret = rep.getSocialProviders().get(k.replace(".key", ".secret")); - - IdentityProviderRepresentation identityProvider = new IdentityProviderRepresentation(); - identityProvider.setAlias(providerId); - identityProvider.setProviderId(providerId); - identityProvider.setEnabled(true); - identityProvider.setLinkOnly(false); - identityProvider.setUpdateProfileFirstLogin(updateProfileFirstLogin); - - Map config = new HashMap<>(); - config.put("clientId", key); - config.put("clientSecret", secret); - identityProvider.setConfig(config); - - identityProviders.add(identityProvider); - } - } - rep.setIdentityProviders(identityProviders); - } - } - } - - private static void convertDeprecatedClientTemplates(RealmRepresentation realm) { - if (realm.getClientTemplates() != null) { - - logger.warnf("Using deprecated 'clientTemplates' configuration in JSON representation for realm '%s'. It will be removed in future versions", realm.getRealm()); - - List clientScopes = new LinkedList<>(); - for (ClientTemplateRepresentation template : realm.getClientTemplates()) { - ClientScopeRepresentation scopeRep = new ClientScopeRepresentation(); - scopeRep.setId(template.getId()); - scopeRep.setName(template.getName()); - scopeRep.setProtocol(template.getProtocol()); - scopeRep.setDescription(template.getDescription()); - scopeRep.setAttributes(template.getAttributes()); - scopeRep.setProtocolMappers(template.getProtocolMappers()); - - clientScopes.add(scopeRep); - } - - realm.setClientScopes(clientScopes); - } - } - - - - protected static void importComponents(RealmModel newRealm, MultivaluedHashMap components, String parentId) { - for (Map.Entry> entry : components.entrySet()) { - String providerType = entry.getKey(); - for (ComponentExportRepresentation compRep : entry.getValue()) { - ComponentModel component = new ComponentModel(); - component.setId(compRep.getId()); - component.setName(compRep.getName()); - component.setConfig(compRep.getConfig()); - component.setProviderType(providerType); - component.setProviderId(compRep.getProviderId()); - component.setSubType(compRep.getSubType()); - component.setParentId(parentId); - component = newRealm.importComponentModel(component); - if (compRep.getSubComponents() != null) { - importComponents(newRealm, compRep.getSubComponents(), component.getId()); - } - } - } - } - - public static void importGroups(RealmModel realm, RealmRepresentation rep) { - List groups = rep.getGroups(); - if (groups == null) return; - - GroupModel parent = null; - for (GroupRepresentation group : groups) { - importGroup(realm, parent, group); - } - } - - private static WebAuthnPolicy getWebAuthnPolicyTwoFactor(RealmRepresentation rep) { - WebAuthnPolicy webAuthnPolicy = new WebAuthnPolicy(); - - String webAuthnPolicyRpEntityName = rep.getWebAuthnPolicyRpEntityName(); - if (webAuthnPolicyRpEntityName == null || webAuthnPolicyRpEntityName.isEmpty()) - webAuthnPolicyRpEntityName = Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME; - webAuthnPolicy.setRpEntityName(webAuthnPolicyRpEntityName); - - List webAuthnPolicySignatureAlgorithms = rep.getWebAuthnPolicySignatureAlgorithms(); - if (webAuthnPolicySignatureAlgorithms == null || webAuthnPolicySignatureAlgorithms.isEmpty()) - webAuthnPolicySignatureAlgorithms = Arrays.asList(Constants.DEFAULT_WEBAUTHN_POLICY_SIGNATURE_ALGORITHMS.split(",")); - webAuthnPolicy.setSignatureAlgorithm(webAuthnPolicySignatureAlgorithms); - - String webAuthnPolicyRpId = rep.getWebAuthnPolicyRpId(); - if (webAuthnPolicyRpId == null || webAuthnPolicyRpId.isEmpty()) - webAuthnPolicyRpId = ""; - webAuthnPolicy.setRpId(webAuthnPolicyRpId); - - String webAuthnPolicyAttestationConveyancePreference = rep.getWebAuthnPolicyAttestationConveyancePreference(); - if (webAuthnPolicyAttestationConveyancePreference == null || webAuthnPolicyAttestationConveyancePreference.isEmpty()) - webAuthnPolicyAttestationConveyancePreference = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setAttestationConveyancePreference(webAuthnPolicyAttestationConveyancePreference); - - String webAuthnPolicyAuthenticatorAttachment = rep.getWebAuthnPolicyAuthenticatorAttachment(); - if (webAuthnPolicyAuthenticatorAttachment == null || webAuthnPolicyAuthenticatorAttachment.isEmpty()) - webAuthnPolicyAuthenticatorAttachment = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setAuthenticatorAttachment(webAuthnPolicyAuthenticatorAttachment); - - String webAuthnPolicyRequireResidentKey = rep.getWebAuthnPolicyRequireResidentKey(); - if (webAuthnPolicyRequireResidentKey == null || webAuthnPolicyRequireResidentKey.isEmpty()) - webAuthnPolicyRequireResidentKey = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setRequireResidentKey(webAuthnPolicyRequireResidentKey); - - String webAuthnPolicyUserVerificationRequirement = rep.getWebAuthnPolicyUserVerificationRequirement(); - if (webAuthnPolicyUserVerificationRequirement == null || webAuthnPolicyUserVerificationRequirement.isEmpty()) - webAuthnPolicyUserVerificationRequirement = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setUserVerificationRequirement(webAuthnPolicyUserVerificationRequirement); - - Integer webAuthnPolicyCreateTimeout = rep.getWebAuthnPolicyCreateTimeout(); - if (webAuthnPolicyCreateTimeout != null) webAuthnPolicy.setCreateTimeout(webAuthnPolicyCreateTimeout); - else webAuthnPolicy.setCreateTimeout(0); - - Boolean webAuthnPolicyAvoidSameAuthenticatorRegister = rep.isWebAuthnPolicyAvoidSameAuthenticatorRegister(); - if (webAuthnPolicyAvoidSameAuthenticatorRegister != null) webAuthnPolicy.setAvoidSameAuthenticatorRegister(webAuthnPolicyAvoidSameAuthenticatorRegister); - - List webAuthnPolicyAcceptableAaguids = rep.getWebAuthnPolicyAcceptableAaguids(); - if (webAuthnPolicyAcceptableAaguids != null) webAuthnPolicy.setAcceptableAaguids(webAuthnPolicyAcceptableAaguids); - - List webAuthnPolicyExtraOrigins = rep.getWebAuthnPolicyExtraOrigins(); - if (webAuthnPolicyExtraOrigins != null) webAuthnPolicy.setExtraOrigins(webAuthnPolicyExtraOrigins); - - return webAuthnPolicy; - } - - - private static WebAuthnPolicy getWebAuthnPolicyPasswordless(RealmRepresentation rep) { - WebAuthnPolicy webAuthnPolicy = new WebAuthnPolicy(); - - String webAuthnPolicyRpEntityName = rep.getWebAuthnPolicyPasswordlessRpEntityName(); - if (webAuthnPolicyRpEntityName == null || webAuthnPolicyRpEntityName.isEmpty()) - webAuthnPolicyRpEntityName = Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME; - webAuthnPolicy.setRpEntityName(webAuthnPolicyRpEntityName); - - List webAuthnPolicySignatureAlgorithms = rep.getWebAuthnPolicyPasswordlessSignatureAlgorithms(); - if (webAuthnPolicySignatureAlgorithms == null || webAuthnPolicySignatureAlgorithms.isEmpty()) - webAuthnPolicySignatureAlgorithms = Arrays.asList(Constants.DEFAULT_WEBAUTHN_POLICY_SIGNATURE_ALGORITHMS.split(",")); - webAuthnPolicy.setSignatureAlgorithm(webAuthnPolicySignatureAlgorithms); - - String webAuthnPolicyRpId = rep.getWebAuthnPolicyPasswordlessRpId(); - if (webAuthnPolicyRpId == null || webAuthnPolicyRpId.isEmpty()) - webAuthnPolicyRpId = ""; - webAuthnPolicy.setRpId(webAuthnPolicyRpId); - - String webAuthnPolicyAttestationConveyancePreference = rep.getWebAuthnPolicyPasswordlessAttestationConveyancePreference(); - if (webAuthnPolicyAttestationConveyancePreference == null || webAuthnPolicyAttestationConveyancePreference.isEmpty()) - webAuthnPolicyAttestationConveyancePreference = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setAttestationConveyancePreference(webAuthnPolicyAttestationConveyancePreference); - - String webAuthnPolicyAuthenticatorAttachment = rep.getWebAuthnPolicyPasswordlessAuthenticatorAttachment(); - if (webAuthnPolicyAuthenticatorAttachment == null || webAuthnPolicyAuthenticatorAttachment.isEmpty()) - webAuthnPolicyAuthenticatorAttachment = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setAuthenticatorAttachment(webAuthnPolicyAuthenticatorAttachment); - - String webAuthnPolicyRequireResidentKey = rep.getWebAuthnPolicyPasswordlessRequireResidentKey(); - if (webAuthnPolicyRequireResidentKey == null || webAuthnPolicyRequireResidentKey.isEmpty()) - webAuthnPolicyRequireResidentKey = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setRequireResidentKey(webAuthnPolicyRequireResidentKey); - - String webAuthnPolicyUserVerificationRequirement = rep.getWebAuthnPolicyPasswordlessUserVerificationRequirement(); - if (webAuthnPolicyUserVerificationRequirement == null || webAuthnPolicyUserVerificationRequirement.isEmpty()) - webAuthnPolicyUserVerificationRequirement = Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; - webAuthnPolicy.setUserVerificationRequirement(webAuthnPolicyUserVerificationRequirement); - - Integer webAuthnPolicyCreateTimeout = rep.getWebAuthnPolicyPasswordlessCreateTimeout(); - if (webAuthnPolicyCreateTimeout != null) webAuthnPolicy.setCreateTimeout(webAuthnPolicyCreateTimeout); - else webAuthnPolicy.setCreateTimeout(0); - - Boolean webAuthnPolicyAvoidSameAuthenticatorRegister = rep.isWebAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister(); - if (webAuthnPolicyAvoidSameAuthenticatorRegister != null) webAuthnPolicy.setAvoidSameAuthenticatorRegister(webAuthnPolicyAvoidSameAuthenticatorRegister); - - List webAuthnPolicyAcceptableAaguids = rep.getWebAuthnPolicyPasswordlessAcceptableAaguids(); - if (webAuthnPolicyAcceptableAaguids != null) webAuthnPolicy.setAcceptableAaguids(webAuthnPolicyAcceptableAaguids); - - List webAuthnPolicyExtraOrigins = rep.getWebAuthnPolicyPasswordlessExtraOrigins(); - if (webAuthnPolicyExtraOrigins != null) webAuthnPolicy.setExtraOrigins(webAuthnPolicyExtraOrigins); - - return webAuthnPolicy; - } - public static Map importAuthenticationFlows(KeycloakSession session, RealmModel newRealm, RealmRepresentation rep) { - Map mappedFlows = new HashMap<>(); - if (rep.getAuthenticationFlows() == null) { - // assume this is an old version being imported - DefaultAuthenticationFlows.migrateFlows(newRealm); - } else { - if (rep.getAuthenticatorConfig() != null) { - for (AuthenticatorConfigRepresentation configRep : rep.getAuthenticatorConfig()) { - if (configRep.getAlias() == null) { - // this can happen only during import json files from keycloak 3.4.0 and older - throw new IllegalStateException("Provided realm contains authenticator config with null alias. " - + "It should be resolved by adding alias to the authenticator config before exporting the realm."); - } - AuthenticatorConfigModel model = RepresentationToModel.toModel(configRep); - newRealm.addAuthenticatorConfig(model); - } - } - if (rep.getAuthenticationFlows() != null) { - for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { - AuthenticationFlowModel model = RepresentationToModel.toModel(flowRep); - String previousId = model.getId(); - model = newRealm.addAuthenticationFlow(model); - // store the mapped ids so that clients can reference the correct flow when importing the authenticationFlowBindingOverrides - mappedFlows.put(previousId, model.getId()); - } - for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { - AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); - for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) { - AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep); - newRealm.addAuthenticatorExecution(execution); - } - } - } - } - if (rep.getBrowserFlow() == null) { - AuthenticationFlowModel defaultFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); - if (defaultFlow != null) { - newRealm.setBrowserFlow(defaultFlow); - } - } else { - newRealm.setBrowserFlow(newRealm.getFlowByAlias(rep.getBrowserFlow())); - } - if (rep.getRegistrationFlow() == null) { - AuthenticationFlowModel defaultFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.REGISTRATION_FLOW); - if (defaultFlow != null) { - newRealm.setRegistrationFlow(defaultFlow); - } - } else { - newRealm.setRegistrationFlow(newRealm.getFlowByAlias(rep.getRegistrationFlow())); - } - if (rep.getDirectGrantFlow() == null) { - AuthenticationFlowModel defaultFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW); - if (defaultFlow != null) { - newRealm.setDirectGrantFlow(defaultFlow); - } - } else { - newRealm.setDirectGrantFlow(newRealm.getFlowByAlias(rep.getDirectGrantFlow())); - } - - // reset credentials + client flow needs to be more defensive as they were added later (in 1.5 ) - if (rep.getResetCredentialsFlow() == null) { - AuthenticationFlowModel resetFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW); - if (resetFlow == null) { - DefaultAuthenticationFlows.resetCredentialsFlow(newRealm); - } else { - newRealm.setResetCredentialsFlow(resetFlow); - } - } else { - newRealm.setResetCredentialsFlow(newRealm.getFlowByAlias(rep.getResetCredentialsFlow())); - } - if (rep.getClientAuthenticationFlow() == null) { - AuthenticationFlowModel clientFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.CLIENT_AUTHENTICATION_FLOW); - if (clientFlow == null) { - DefaultAuthenticationFlows.clientAuthFlow(newRealm); - } else { - newRealm.setClientAuthenticationFlow(clientFlow); - } - } else { - newRealm.setClientAuthenticationFlow(newRealm.getFlowByAlias(rep.getClientAuthenticationFlow())); - } - - // Added in 1.7 - if (newRealm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW) == null) { - DefaultAuthenticationFlows.firstBrokerLoginFlow(newRealm, true); - } - - // Added in 2.2 - String defaultProvider = null; - if (rep.getIdentityProviders() != null) { - for (IdentityProviderRepresentation i : rep.getIdentityProviders()) { - if (i.isEnabled() && i.isAuthenticateByDefault()) { - defaultProvider = i.getProviderId(); - break; - } - } - } - - // Added in 3.2 - if (rep.getDockerAuthenticationFlow() == null) { - AuthenticationFlowModel dockerAuthenticationFlow = newRealm.getFlowByAlias(DefaultAuthenticationFlows.DOCKER_AUTH); - if (dockerAuthenticationFlow == null) { - DefaultAuthenticationFlows.dockerAuthenticationFlow(newRealm); - } else { - newRealm.setDockerAuthenticationFlow(dockerAuthenticationFlow); - } - } else { - newRealm.setDockerAuthenticationFlow(newRealm.getFlowByAlias(rep.getDockerAuthenticationFlow())); - } - - DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider); - - return mappedFlows; - } - - private static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { - AuthenticationExecutionModel model = new AuthenticationExecutionModel(); - if (rep.getAuthenticatorConfig() != null) { - AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig()); - model.setAuthenticatorConfig(config.getId()); - } - model.setAuthenticator(rep.getAuthenticator()); - model.setAuthenticatorFlow(rep.isAuthenticatorFlow()); - if (rep.getFlowAlias() != null) { - AuthenticationFlowModel flow = realm.getFlowByAlias(rep.getFlowAlias()); - model.setFlowId(flow.getId()); - } - model.setPriority(rep.getPriority()); - model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement())); - model.setParentFlow(parentFlow.getId()); - return model; - } - - private static void updateCibaSettings(RealmRepresentation rep, RealmModel realm) { - Map newAttributes = rep.getAttributesOrEmpty(); - CibaConfig cibaPolicy = realm.getCibaPolicy(); - - cibaPolicy.setBackchannelTokenDeliveryMode(newAttributes.get(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE)); - cibaPolicy.setExpiresIn(newAttributes.get(CibaConfig.CIBA_EXPIRES_IN)); - cibaPolicy.setPoolingInterval(newAttributes.get(CibaConfig.CIBA_INTERVAL)); - cibaPolicy.setAuthRequestedUserHint(newAttributes.get(CibaConfig.CIBA_AUTH_REQUESTED_USER_HINT)); - } - - private static void updateParSettings(RealmRepresentation rep, RealmModel realm) { - Map newAttributes = rep.getAttributesOrEmpty(); - ParConfig parPolicy = realm.getParPolicy(); - - parPolicy.setRequestUriLifespan(newAttributes.get(ParConfig.PAR_REQUEST_URI_LIFESPAN)); - } - - public static OTPPolicy toPolicy(RealmRepresentation rep) { - OTPPolicy policy = new OTPPolicy(); - if (rep.getOtpPolicyType() != null) policy.setType(rep.getOtpPolicyType()); - if (rep.getOtpPolicyLookAheadWindow() != null) policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow()); - if (rep.getOtpPolicyInitialCounter() != null) policy.setInitialCounter(rep.getOtpPolicyInitialCounter()); - if (rep.getOtpPolicyAlgorithm() != null) policy.setAlgorithm(rep.getOtpPolicyAlgorithm()); - if (rep.getOtpPolicyDigits() != null) policy.setDigits(rep.getOtpPolicyDigits()); - if (rep.getOtpPolicyPeriod() != null) policy.setPeriod(rep.getOtpPolicyPeriod()); - if (rep.isOtpPolicyCodeReusable() != null) policy.setCodeReusable(rep.isOtpPolicyCodeReusable()); - return policy; - } - - - public static RequiredActionProviderModel toModel(RequiredActionProviderRepresentation rep) { - RequiredActionProviderModel model = new RequiredActionProviderModel(); - model.setConfig(RepresentationToModel.removeEmptyString(rep.getConfig())); - model.setPriority(rep.getPriority()); - model.setDefaultAction(rep.isDefaultAction()); - model.setEnabled(rep.isEnabled()); - model.setProviderId(rep.getProviderId()); - model.setName(rep.getName()); - model.setAlias(rep.getAlias()); - return model; - } - - - public static void importRealmAuthorizationSettings(RealmRepresentation rep, RealmModel newRealm, KeycloakSession session) { - if (rep.getClients() != null) { - rep.getClients().forEach(clientRepresentation -> { - ClientModel client = newRealm.getClientByClientId(clientRepresentation.getClientId()); - RepresentationToModel.importAuthorizationSettings(clientRepresentation, client, session); - }); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/deploymentState/MapDeploymentStateProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/deploymentState/MapDeploymentStateProviderFactory.java deleted file mode 100644 index 72a43b8de45..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/deploymentState/MapDeploymentStateProviderFactory.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.deploymentState; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.common.Version; -import org.keycloak.common.util.Base64Url; -import org.keycloak.common.util.SecretGenerator; -import org.keycloak.migration.MigrationModel; -import org.keycloak.migration.ModelVersion; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.DeploymentStateProvider; -import org.keycloak.models.DeploymentStateProviderFactory; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -public class MapDeploymentStateProviderFactory implements DeploymentStateProviderFactory, EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "map"; - - private static final String RESOURCES_VERSION_SEED = "resourcesVersionSeed"; - - @Override - public DeploymentStateProvider create(KeycloakSession session) { - return INSTANCE; - } - - @Override - public void init(Config.Scope config) { - String seed = config.get(RESOURCES_VERSION_SEED); - if (seed == null) { - // hardcoded until https://github.com/keycloak/keycloak/issues/13828 has been implemented - Logger.getLogger(DeploymentStateProviderFactory.class) - .warnf("Version seed for deployment state set with a random number. Caution: This can lead to unstable operations when serving resources from the cluster without a sticky loadbalancer or when restarting nodes. Set the 'storage-deployment-state-version-seed' option with a secret seed to ensure stable operations.", RESOURCES_VERSION_SEED, PROVIDER_ID, DeploymentStateSpi.NAME); - //generate random string for this installation - seed = SecretGenerator.getInstance().randomString(10); - } - try { - Version.RESOURCES_VERSION = Base64Url.encode(MessageDigest.getInstance("SHA-256") - .digest((seed + Version.RESOURCES_VERSION).getBytes())) - .substring(0, 5); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public void close() { - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - private static final DeploymentStateProvider INSTANCE = new DeploymentStateProvider() { - - private final MigrationModel INSTANCE = new MigrationModel() { - @Override - public String getStoredVersion() { - return null; - } - @Override - public String getResourcesTag() { - throw new UnsupportedOperationException("Not supported."); - } - @Override - public void setStoredVersion(String version) { - throw new UnsupportedOperationException("Not supported."); - } - }; - - @Override - public MigrationModel getMigrationModel() { - return INSTANCE; - } - - @Override - public void close() { - } - - }; -} diff --git a/model/map/src/main/java/org/keycloak/models/map/events/EventUtils.java b/model/map/src/main/java/org/keycloak/models/map/events/EventUtils.java deleted file mode 100644 index 51285504863..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/EventUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.keycloak.events.Event; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.events.admin.AuthDetails; - -import org.keycloak.models.map.common.DeepCloner; -import java.util.Collections; -import java.util.Map; - -public class EventUtils { - public static Event entityToModel(MapAuthEventEntity eventEntity) { - Event event = new Event(); - event.setId(eventEntity.getId()); - event.setTime(eventEntity.getTimestamp()); - event.setType(eventEntity.getType()); - event.setRealmId(eventEntity.getRealmId()); - event.setClientId(eventEntity.getClientId()); - event.setUserId(eventEntity.getUserId()); - event.setSessionId(eventEntity.getSessionId()); - event.setIpAddress(eventEntity.getIpAddress()); - event.setError(eventEntity.getError()); - - Map details = eventEntity.getDetails(); - event.setDetails(details == null ? Collections.emptyMap() : details); - - return event; - } - - public static AdminEvent entityToModel(MapAdminEventEntity adminEventEntity) { - AdminEvent adminEvent = new AdminEvent(); - adminEvent.setId(adminEventEntity.getId()); - adminEvent.setTime(adminEventEntity.getTimestamp()); - adminEvent.setRealmId(adminEventEntity.getRealmId()); - setAuthDetails(adminEvent, adminEventEntity); - adminEvent.setOperationType(adminEventEntity.getOperationType()); - adminEvent.setResourceTypeAsString(adminEventEntity.getResourceType()); - adminEvent.setResourcePath(adminEventEntity.getResourcePath()); - adminEvent.setError(adminEventEntity.getError()); - - if(adminEventEntity.getRepresentation() != null) { - adminEvent.setRepresentation(adminEventEntity.getRepresentation()); - } - return adminEvent; - - } - - public static MapAdminEventEntity modelToEntity(AdminEvent adminEvent, boolean includeRepresentation) { - MapAdminEventEntity mapAdminEvent = DeepCloner.DUMB_CLONER.newInstance(MapAdminEventEntity.class); - mapAdminEvent.setId(adminEvent.getId()); - mapAdminEvent.setTimestamp(adminEvent.getTime()); - mapAdminEvent.setRealmId(adminEvent.getRealmId()); - setAuthDetails(mapAdminEvent, adminEvent.getAuthDetails()); - mapAdminEvent.setOperationType(adminEvent.getOperationType()); - mapAdminEvent.setResourceType(adminEvent.getResourceTypeAsString()); - mapAdminEvent.setResourcePath(adminEvent.getResourcePath()); - mapAdminEvent.setError(adminEvent.getError()); - - if(includeRepresentation) { - mapAdminEvent.setRepresentation(adminEvent.getRepresentation()); - } - return mapAdminEvent; - } - - public static MapAuthEventEntity modelToEntity(Event event) { - MapAuthEventEntity eventEntity = DeepCloner.DUMB_CLONER.newInstance(MapAuthEventEntity.class); - eventEntity.setId(event.getId()); - eventEntity.setTimestamp(event.getTime()); - eventEntity.setType(event.getType()); - eventEntity.setRealmId(event.getRealmId()); - eventEntity.setClientId(event.getClientId()); - eventEntity.setUserId(event.getUserId()); - eventEntity.setSessionId(event.getSessionId()); - eventEntity.setIpAddress(event.getIpAddress()); - eventEntity.setError(event.getError()); - eventEntity.setDetails(event.getDetails()); - return eventEntity; - } - - private static void setAuthDetails(MapAdminEventEntity adminEventEntity, AuthDetails authDetails) { - if (authDetails == null) return; - adminEventEntity.setAuthRealmId(authDetails.getRealmId()); - adminEventEntity.setAuthClientId(authDetails.getClientId()); - adminEventEntity.setAuthUserId(authDetails.getUserId()); - adminEventEntity.setAuthIpAddress(authDetails.getIpAddress()); - } - - private static void setAuthDetails(AdminEvent adminEvent, MapAdminEventEntity adminEventEntity) { - AuthDetails authDetails = new AuthDetails(); - authDetails.setRealmId(adminEventEntity.getAuthRealmId()); - authDetails.setClientId(adminEventEntity.getAuthClientId()); - authDetails.setUserId(adminEventEntity.getAuthUserId()); - authDetails.setIpAddress(adminEventEntity.getAuthIpAddress()); - adminEvent.setAuthDetails(authDetails); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/events/MapAdminEventEntity.java b/model/map/src/main/java/org/keycloak/models/map/events/MapAdminEventEntity.java deleted file mode 100644 index 3081a569c6b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/MapAdminEventEntity.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.keycloak.events.admin.OperationType; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.events.MapAdminEventEntity.AbstractAdminEventEntity" -) -@DeepCloner.Root -public interface MapAdminEventEntity extends UpdatableEntity, AbstractEntity, ExpirableEntity { - - public abstract class AbstractAdminEventEntity extends UpdatableEntity.Impl implements MapAdminEventEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - } - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the event was created. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when this entity was created. - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - String getRealmId(); - void setRealmId(String realmId); - - OperationType getOperationType(); - void setOperationType(OperationType operationType); - - String getResourcePath(); - void setResourcePath(String resourcePath); - - String getRepresentation(); - void setRepresentation(String representation); - - String getError(); - void setError(String error); - - String getResourceType(); - void setResourceType(String resourceType); - - String getAuthRealmId(); - void setAuthRealmId(String realmId); - - String getAuthClientId(); - void setAuthClientId(String clientId); - - String getAuthUserId(); - void setAuthUserId(String userId); - - String getAuthIpAddress(); - void setAuthIpAddress(String ipAddress); -} \ No newline at end of file diff --git a/model/map/src/main/java/org/keycloak/models/map/events/MapAdminEventQuery.java b/model/map/src/main/java/org/keycloak/models/map/events/MapAdminEventQuery.java deleted file mode 100644 index 74e17697995..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/MapAdminEventQuery.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.events.admin.AdminEvent.SearchableFields; -import org.keycloak.events.admin.AdminEventQuery; -import org.keycloak.events.admin.OperationType; -import org.keycloak.events.admin.ResourceType; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.Arrays; -import java.util.Date; -import java.util.stream.Stream; - -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.EQ; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.GE; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LE; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LIKE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.Order.DESCENDING; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public abstract class MapAdminEventQuery implements AdminEventQuery { - - private Integer firstResult; - private Integer maxResults; - private QueryParameters.Order order = DESCENDING; - private DefaultModelCriteria mcb = criteria(); - protected String realmId; - - @Override - public AdminEventQuery realm(String realmId) { - mcb = mcb.compare(SearchableFields.REALM_ID, EQ, realmId); - this.realmId = realmId; - return this; - } - - @Override - public AdminEventQuery authRealm(String realmId) { - mcb = mcb.compare(SearchableFields.AUTH_REALM_ID, EQ, realmId); - return this; - } - - @Override - public AdminEventQuery authClient(String clientId) { - mcb = mcb.compare(SearchableFields.AUTH_CLIENT_ID, EQ, clientId); - return this; - } - - @Override - public AdminEventQuery authUser(String userId) { - mcb = mcb.compare(SearchableFields.AUTH_USER_ID, EQ, userId); - return this; - } - - @Override - public AdminEventQuery authIpAddress(String ipAddress) { - mcb = mcb.compare(SearchableFields.AUTH_IP_ADDRESS, EQ, ipAddress); - return this; - } - - @Override - public AdminEventQuery operation(OperationType... operations) { - mcb = mcb.compare(SearchableFields.OPERATION_TYPE, IN, Arrays.stream(operations)); - return this; - } - - @Override - public AdminEventQuery resourceType(ResourceType... resourceTypes) { - mcb = mcb.compare(SearchableFields.RESOURCE_TYPE, IN, Arrays.stream(resourceTypes)); - return this; - } - - @Override - public AdminEventQuery resourcePath(String resourcePath) { - mcb = mcb.compare(SearchableFields.RESOURCE_PATH, LIKE, resourcePath.replace('*', '%')); - return this; - } - - @Override - public AdminEventQuery fromTime(Date fromTime) { - mcb = mcb.compare(SearchableFields.TIMESTAMP, GE, fromTime.getTime()); - return this; - } - - @Override - public AdminEventQuery toTime(Date toTime) { - mcb = mcb.compare(SearchableFields.TIMESTAMP, LE, toTime.getTime()); - return this; - } - - @Override - public AdminEventQuery firstResult(int first) { - firstResult = first; - return this; - } - - @Override - public AdminEventQuery maxResults(int max) { - maxResults = max; - return this; - } - - @Override - public AdminEventQuery orderByDescTime() { - order = DESCENDING; - return this; - } - - @Override - public AdminEventQuery orderByAscTime() { - order = ASCENDING; - return this; - } - - @Override - public Stream getResultStream() { - return read(QueryParameters.withCriteria(mcb) - .offset(firstResult) - .limit(maxResults) - .orderBy(SearchableFields.TIMESTAMP, order) - ); - } - - protected abstract Stream read(QueryParameters queryParameters); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/events/MapAuthEventEntity.java b/model/map/src/main/java/org/keycloak/models/map/events/MapAuthEventEntity.java deleted file mode 100644 index 9c9647e5d91..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/MapAuthEventEntity.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.keycloak.events.EventType; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Map; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.events.MapAuthEventEntity.AbstractAuthEventEntity" -) -@DeepCloner.Root -public interface MapAuthEventEntity extends UpdatableEntity, AbstractEntity, ExpirableEntity { - - public abstract class AbstractAuthEventEntity extends UpdatableEntity.Impl implements MapAuthEventEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - } - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the event entity was created. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the event entity was created. - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - EventType getType(); - void setType(EventType type); - - String getRealmId(); - void setRealmId(String realmId); - - String getClientId(); - void setClientId(String clientId); - - String getUserId(); - void setUserId(String userId); - - String getSessionId(); - void setSessionId(String sessionId); - - String getIpAddress(); - void setIpAddress(String ipAddress); - - String getError(); - void setError(String error); - - Map getDetails(); - void setDetails(Map details); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/events/MapAuthEventQuery.java b/model/map/src/main/java/org/keycloak/models/map/events/MapAuthEventQuery.java deleted file mode 100644 index bb41fb79130..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/MapAuthEventQuery.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.keycloak.events.Event; -import org.keycloak.events.Event.SearchableFields; -import org.keycloak.events.EventQuery; -import org.keycloak.events.EventType; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.Arrays; -import java.util.Date; -import java.util.stream.Stream; - -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.EQ; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.GE; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN; -import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.Order.DESCENDING; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public abstract class MapAuthEventQuery implements EventQuery { - - private Integer firstResult; - private Integer maxResults; - private QueryParameters.Order order = DESCENDING; - private DefaultModelCriteria mcb = criteria(); - protected String realmId; - - @Override - public EventQuery type(EventType... types) { - mcb = mcb.compare(SearchableFields.EVENT_TYPE, IN, Arrays.asList(types)); - return this; - } - - @Override - public EventQuery realm(String realmId) { - mcb = mcb.compare(SearchableFields.REALM_ID, EQ, realmId); - this.realmId = realmId; - return this; - } - - @Override - public EventQuery client(String clientId) { - mcb = mcb.compare(SearchableFields.CLIENT_ID, EQ, clientId); - return this; - } - - @Override - public EventQuery user(String userId) { - mcb = mcb.compare(SearchableFields.USER_ID, EQ, userId); - return this; - } - - @Override - public EventQuery fromDate(Date fromDate) { - mcb = mcb.compare(SearchableFields.TIMESTAMP, GE, fromDate.getTime()); - return this; - } - - @Override - public EventQuery toDate(Date toDate) { - mcb = mcb.compare(SearchableFields.TIMESTAMP, LE, toDate.getTime()); - return this; - } - - @Override - public EventQuery ipAddress(String ipAddress) { - mcb = mcb.compare(SearchableFields.IP_ADDRESS, EQ, ipAddress); - return this; - } - - @Override - public EventQuery firstResult(int firstResult) { - this.firstResult = firstResult; - return this; - } - - @Override - public EventQuery maxResults(int max) { - this.maxResults = max; - return this; - } - - @Override - public EventQuery orderByDescTime() { - order = DESCENDING; - return this; - } - - @Override - public EventQuery orderByAscTime() { - order = ASCENDING; - return this; - } - - @Override - public Stream getResultStream() { - return read(QueryParameters.withCriteria(mcb) - .offset(firstResult) - .limit(maxResults) - .orderBy(SearchableFields.TIMESTAMP, order)); - } - - protected abstract Stream read(QueryParameters queryParameters); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/events/MapEventStoreProvider.java b/model/map/src/main/java/org/keycloak/models/map/events/MapEventStoreProvider.java deleted file mode 100644 index 2998250e595..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/MapEventStoreProvider.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.events.Event; -import org.keycloak.events.EventQuery; -import org.keycloak.events.EventStoreProvider; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.events.admin.AdminEventQuery; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.stream.Stream; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.models.map.events.EventUtils.modelToEntity; - -public class MapEventStoreProvider implements EventStoreProvider { - - private static final Logger LOG = Logger.getLogger(MapEventStoreProvider.class); - private final KeycloakSession session; - private final MapStorage authEventsTX; - private final MapStorage adminEventsTX; - private final boolean adminTxHasRealmId; - private final boolean authTxHasRealmId; - - public MapEventStoreProvider(KeycloakSession session, MapStorage loginEventsStore, MapStorage adminEventsStore) { - this.session = session; - this.authEventsTX = loginEventsStore; - this.adminEventsTX = adminEventsStore; - this.authTxHasRealmId = this.authEventsTX instanceof HasRealmId; - this.adminTxHasRealmId = this.adminEventsTX instanceof HasRealmId; - } - - private MapStorage adminTxInRealm(String realmId) { - if (adminTxHasRealmId) { - ((HasRealmId) adminEventsTX).setRealmId(realmId); - } - return adminEventsTX; - } - - private MapStorage adminTxInRealm(RealmModel realm) { - return adminTxInRealm(realm == null ? null : realm.getId()); - } - - private MapStorage authTxInRealm(String realmId) { - if (authTxHasRealmId) { - ((HasRealmId) authEventsTX).setRealmId(realmId); - } - return authEventsTX; - } - - private MapStorage authTxInRealm(RealmModel realm) { - return authTxInRealm(realm == null ? null : realm.getId()); - } - - /** LOGIN EVENTS **/ - @Override - public void onEvent(Event event) { - LOG.tracef("onEvent(%s)%s", event, getShortStackTrace()); - String id = event.getId(); - String realmId = event.getRealmId(); - - if (id != null && authTxInRealm(realmId).exists(id)) { - throw new ModelDuplicateException("Event already exists: " + id); - } - - MapAuthEventEntity entity = modelToEntity(event); - if (realmId != null) { - RealmModel realm = session.realms().getRealm(realmId); - if (realm != null && realm.getEventsExpiration() > 0) { - entity.setExpiration(Time.currentTimeMillis() + (realm.getEventsExpiration() * 1000)); - } - } - - authTxInRealm(realmId).create(entity); - } - - @Override - public EventQuery createQuery() { - LOG.tracef("createQuery()%s", getShortStackTrace()); - return new MapAuthEventQuery() { - private boolean filterExpired(ExpirableEntity event) { - // Check if entity is expired - if (isExpired(event, true)) { - // Remove entity - authTxInRealm(realmId).delete(event.getId()); - - return false; // Do not include entity in the resulting stream - } - - return true; // Entity is not expired - } - - @Override - protected Stream read(QueryParameters queryParameters) { - return authTxInRealm(realmId).read(queryParameters) - .filter(this::filterExpired) - .map(EventUtils::entityToModel); - } - }; - } - - @Override - public void clear() { - LOG.tracef("clear()%s", getShortStackTrace()); - authTxInRealm((String) null).delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria())); - } - - @Override - public void clear(RealmModel realm) { - LOG.tracef("clear(%s)%s", realm, getShortStackTrace()); - authTxInRealm(realm).delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria() - .compare(Event.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId()))); - } - - @Override - public void clear(RealmModel realm, long olderThan) { - LOG.tracef("clear(%s, %d)%s", realm, olderThan, getShortStackTrace()); - authTxInRealm(realm).delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria() - .compare(Event.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId()) - .compare(Event.SearchableFields.TIMESTAMP, ModelCriteriaBuilder.Operator.LT, olderThan) - )); - } - - @Override - public void clearExpiredEvents() { - LOG.tracef("clearExpiredEvents()%s", getShortStackTrace()); - LOG.warnf("Clearing expired entities should not be triggered manually. It is responsibility of the store to clear these."); - } - - /** ADMIN EVENTS **/ - - @Override - public void onEvent(AdminEvent event, boolean includeRepresentation) { - LOG.tracef("onEvent(%s, %s)%s", event, includeRepresentation, getShortStackTrace()); - String id = event.getId(); - String realmId = event.getRealmId(); - if (id != null && adminTxInRealm(realmId).exists(id)) { - throw new ModelDuplicateException("Event already exists: " + id); - } - MapAdminEventEntity entity = modelToEntity(event,includeRepresentation); - if (realmId != null) { - RealmModel realm = session.realms().getRealm(realmId); - if (realm != null) { - Long expiration = realm.getAttribute("adminEventsExpiration",0L); - if (expiration > 0) { - entity.setExpiration(Time.currentTimeMillis() + (expiration * 1000)); - } - } - } - adminTxInRealm(realmId).create(entity); - } - - @Override - public AdminEventQuery createAdminQuery() { - LOG.tracef("createAdminQuery()%s", getShortStackTrace()); - return new MapAdminEventQuery() { - private boolean filterExpired(ExpirableEntity event) { - // Check if entity is expired - if (isExpired(event, true)) { - // Remove entity - authTxInRealm(realmId).delete(event.getId()); - - return false; // Do not include entity in the resulting stream - } - - return true; // Entity is not expired - } - - @Override - protected Stream read(QueryParameters queryParameters) { - return adminTxInRealm(realmId).read(queryParameters) - .filter(this::filterExpired) - .map(EventUtils::entityToModel); - } - }; - } - - @Override - public void clearAdmin() { - LOG.tracef("clearAdmin()%s", getShortStackTrace()); - adminTxInRealm((String) null).delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria())); - } - - @Override - public void clearAdmin(RealmModel realm) { - LOG.tracef("clearAdmin(%s)%s", realm, getShortStackTrace()); - adminTxInRealm(realm).delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria() - .compare(AdminEvent.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId()))); - } - - @Override - public void clearAdmin(RealmModel realm, long olderThan) { - LOG.tracef("clearAdmin(%s, %d)%s", realm, olderThan, getShortStackTrace()); - adminTxInRealm(realm).delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria() - .compare(AdminEvent.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId()) - .compare(AdminEvent.SearchableFields.TIMESTAMP, ModelCriteriaBuilder.Operator.LT, olderThan) - )); - } - - @Override - public void close() { - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/events/MapEventStoreProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/events/MapEventStoreProviderFactory.java deleted file mode 100644 index 42fedca80ab..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/events/MapEventStoreProviderFactory.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2022 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.events; - -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.events.Event; -import org.keycloak.events.EventStoreProvider; -import org.keycloak.events.EventStoreProviderFactory; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.uniqueCounter; - -public class MapEventStoreProviderFactory implements AmphibianProviderFactory, EnvironmentDependentProviderFactory, EventStoreProviderFactory, InvalidationHandler { - - public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID; - private Config.Scope storageConfigScopeAdminEvents; - private Config.Scope storageConfigScopeLoginEvents; - private final String uniqueKey = getClass().getName() + uniqueCounter.incrementAndGet(); - - - @Override - public void init(Config.Scope config) { - storageConfigScopeAdminEvents = config.scope(AbstractMapProviderFactory.CONFIG_STORAGE + "-admin-events"); - storageConfigScopeLoginEvents = config.scope(AbstractMapProviderFactory.CONFIG_STORAGE + "-auth-events"); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public EventStoreProvider create(KeycloakSession session) { - MapEventStoreProvider provider = session.getAttribute(uniqueKey, MapEventStoreProvider.class); - if (provider != null) return provider; - - final MapStorageProvider factoryAe = AbstractMapProviderFactory.getProviderFactoryOrComponentFactory(session, storageConfigScopeAdminEvents).create(session); - MapStorage adminEventsStore = factoryAe.getMapStorage(AdminEvent.class); - - final MapStorageProvider factoryLe = AbstractMapProviderFactory.getProviderFactoryOrComponentFactory(session, storageConfigScopeLoginEvents).create(session); - MapStorage loginEventsStore = factoryLe.getMapStorage(Event.class); - - provider = new MapEventStoreProvider(session, loginEventsStore, adminEventsStore); - session.setAttribute(uniqueKey, provider); - return provider; - } - - @Override - public void invalidate(KeycloakSession session, InvalidationHandler.InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).clear((RealmModel) params[0]); - create(session).clearAdmin((RealmModel) params[0]); - } - } - - @Override - public void close() { - AmphibianProviderFactory.super.close(); - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public String getHelpText() { - return "Event provider"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java b/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java deleted file mode 100644 index 30a345f96c4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java +++ /dev/null @@ -1,55 +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.group; - -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractGroupModel implements GroupModel { - - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractGroupModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof GroupModel)) return false; - - GroupModel that = (GroupModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupAdapter.java b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupAdapter.java deleted file mode 100644 index 4c2363a0d91..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupAdapter.java +++ /dev/null @@ -1,170 +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.group; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.GroupProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.utils.RoleUtils; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - - -public abstract class MapGroupAdapter extends AbstractGroupModel { - public MapGroupAdapter(KeycloakSession session, RealmModel realm, MapGroupEntity entity) { - super(session, realm, entity); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - entity.setName(name); - } - - @Override - public void setSingleAttribute(String name, String value) { - entity.setAttribute(name, Collections.singletonList(value)); - } - - @Override - public void setAttribute(String name, List values) { - entity.setAttribute(name, values); - } - - @Override - public void removeAttribute(String name) { - entity.removeAttribute(name); - } - - @Override - public String getFirstAttribute(String name) { - return getAttributeStream(name).findFirst().orElse(null); - } - - @Override - public Stream getAttributeStream(String name) { - List attributes = entity.getAttribute(name); - if (attributes == null || attributes.isEmpty()) return Stream.empty(); - return attributes.stream(); - } - - @Override - public Map> getAttributes() { - Map> attrs = entity.getAttributes(); - return attrs == null ? Collections.emptyMap() : attrs; - } - - @Override - public GroupModel getParent() { - String parentId = getParentId(); - if (parentId == null) { - return null; - } - - return session.groups().getGroupById(realm, parentId); - } - - @Override - public String getParentId() { - return entity.getParentId(); - } - - @Override - public void setParent(GroupModel group) { - if (group == null) { - entity.setParentId(null); - return; - } - - if (!getId().equals(group.getId())) { - entity.setParentId(group.getId()); - } - } - - @Override - public void addChild(GroupModel subGroup) { - subGroup.setParent(this); - } - - @Override - public void removeChild(GroupModel subGroup) { - if (getId().equals(subGroup.getParentId())) { - subGroup.setParent(null); - } - } - - @Override - public Stream getRealmRoleMappingsStream() { - return getRoleMappingsStream() - .filter(roleModel -> roleModel.getContainer() instanceof RealmModel); - } - - @Override - public Stream getClientRoleMappingsStream(ClientModel app) { - final String clientId = app.getId(); - return getRoleMappingsStream() - .filter(roleModel -> roleModel.getContainer() instanceof ClientModel) - .filter(roleModel -> roleModel.getContainer().getId().equals(clientId)); - } - - @Override - public boolean hasDirectRole(RoleModel role) { - Set grantedRoles = entity.getGrantedRoles(); - return grantedRoles != null && grantedRoles.contains(role.getId()); - } - - @Override - public boolean hasRole(RoleModel role) { - if (RoleUtils.hasRole(getRoleMappingsStream(), role)) return true; - GroupModel parent = getParent(); - return parent != null && parent.hasRole(role); - } - - @Override - public void grantRole(RoleModel role) { - entity.addGrantedRole(role.getId()); - } - - @Override - public Stream getRoleMappingsStream() { - Set grantedRoles = entity.getGrantedRoles(); - return grantedRoles == null ? Stream.empty() : grantedRoles.stream() - .map(roleId -> session.roles().getRoleById(realm, roleId)); - } - - @Override - public void deleteRoleMapping(RoleModel role) { - entity.removeGrantedRole(role.getId()); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java deleted file mode 100644 index 15a649dc52b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.group; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Set; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.group.MapGroupEntity.AbstractGroupEntity" -) -@DeepCloner.Root -public interface MapGroupEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes { - - public abstract class AbstractGroupEntity extends UpdatableEntity.Impl implements MapGroupEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - } - - String getName(); - void setName(String name); - - String getParentId(); - void setParentId(String parentId); - - String getRealmId(); - void setRealmId(String realmId); - - Set getGrantedRoles(); - void setGrantedRoles(Set grantedRoles); - void addGrantedRole(String role); - void removeGrantedRole(String role); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java deleted file mode 100644 index da301d99da9..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java +++ /dev/null @@ -1,392 +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.group; - -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.UnaryOperator; -import java.util.stream.Stream; -import org.jboss.logging.Logger; -import org.keycloak.models.GroupModel; -import org.keycloak.models.GroupModel.SearchableFields; -import org.keycloak.models.GroupProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.KeycloakModelUtils; - - - - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapGroupProvider implements GroupProvider { - - private static final Logger LOG = Logger.getLogger(MapGroupProvider.class); - private final KeycloakSession session; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapGroupProvider(KeycloakSession session, MapStorage groupStore) { - this.session = session; - this.store = groupStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private Function entityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return origEntity -> new MapGroupAdapter(session, realm, origEntity) { - @Override - public Stream getSubGroupsStream() { - return getGroupsByParentId(realm, this.getId()); - } - }; - } - - @Override - public GroupModel getGroupById(RealmModel realm, String id) { - if (id == null || realm == null) { - return null; - } - - LOG.tracef("getGroupById(%s, %s)%s", realm, id, getShortStackTrace()); - - String realmId = realm.getId(); - MapGroupEntity entity = storeWithRealm(realm).read(id); - return (entity == null || ! Objects.equals(realmId, entity.getRealmId())) - ? null - : entityToAdapterFunc(realm).apply(entity); - } - - @Override - public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) { - if (name == null) { - return null; - } - - LOG.tracef("getGroupByName(%s, %s)%s", realm, name, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.NAME, Operator.EQ, name) - .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - if (parent != null) { - mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, parent.getId()); - } else { - mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS); - } - QueryParameters queryParameters = withCriteria(mcb); - String groupId = storeWithRealm(realm).read(queryParameters).findFirst().map(MapGroupEntity::getId) - .orElse(null); - return groupId == null ? null : session.groups().getGroupById(realm, groupId); - } - - @Override - public Stream getGroupsStream(RealmModel realm) { - return getGroupsStreamInternal(realm, null, null); - } - - private Stream getGroupsStreamInternal(RealmModel realm, UnaryOperator> modifier, UnaryOperator> queryParametersModifier) { - LOG.tracef("getGroupsStream(%s)%s", realm, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - if (modifier != null) { - mcb = modifier.apply(mcb); - } - - QueryParameters queryParameters = withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING); - if (queryParametersModifier != null) { - queryParameters = queryParametersModifier.apply(queryParameters); - } - - return storeWithRealm(realm).read(queryParameters) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream getGroupsStream(RealmModel realm, Stream ids, String search, Integer first, Integer max) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.ID, Operator.IN, ids) - .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - if (search != null) { - mcb = mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); - } - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) { - LOG.tracef("getGroupsCount(%s, %s)%s", realm, onlyTopGroups, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - if (Objects.equals(onlyTopGroups, Boolean.TRUE)) { - mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS); - } - - return storeWithRealm(realm).getCount(withCriteria(mcb)); - } - - @Override - public Long getGroupsCountByNameContaining(RealmModel realm, String search) { - LOG.tracef("getGroupsCountByNameContaining(%s, %s, %s)%s", realm, session, search, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)).count(); - } - - @Override - public Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) { - LOG.tracef("getGroupsByRole(%s, %s, %d, %d)%s", realm, role, firstResult, maxResults, getShortStackTrace()); - return getGroupsStreamInternal(realm, - (DefaultModelCriteria mcb) -> mcb.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()), - qp -> qp.offset(firstResult).limit(maxResults) - ); - } - - @Override - public Stream getTopLevelGroupsStream(RealmModel realm, String search, Boolean exact, Integer firstResult, Integer maxResults) { - LOG.tracef("getTopLevelGroupsStream(%s, %s,%s, %s,%s)%s", realm, search, exact, firstResult, maxResults, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS); - if(Boolean.TRUE.equals(exact)) { - mcb.compare(SearchableFields.NAME, Operator.EQ,search); - } else { - mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); - } - - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream searchForGroupByNameStream(RealmModel realm, String search, Boolean exact, Integer firstResult, Integer maxResults) { - LOG.tracef("searchForGroupByNameStream(%s, %s, %s, %b, %d, %d)%s", realm, session, search, exact, firstResult, maxResults, getShortStackTrace()); - - - DefaultModelCriteria mcb = criteria(); - if (exact != null && exact.equals(Boolean.TRUE)) { - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.NAME, Operator.EQ, search); - } else { - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); - } - - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream searchGroupsByAttributes(RealmModel realm, Map attributes, Integer firstResult, Integer maxResults) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(GroupModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - for (Map.Entry entry : attributes.entrySet()) { - mcb = mcb.compare(GroupModel.SearchableFields.ATTRIBUTE, Operator.EQ, entry.getKey(), entry.getValue()); - } - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) { - LOG.tracef("createGroup(%s, %s, %s, %s)%s", realm, id, name, toParent, getShortStackTrace()); - // Check Db constraint: uniqueConstraints = { @UniqueConstraint(columnNames = {"REALM_ID", "PARENT_GROUP", "NAME"})} - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.NAME, Operator.EQ, name); - - mcb = toParent == null ? - mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS) : - mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, toParent.getId()); - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("Group with name '" + name + "' in realm " + realm.getName() + " already exists for requested parent" ); - } - - MapGroupEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapGroupEntity.class); - entity.setId(id); - entity.setRealmId(realm.getId()); - entity.setName(name); - entity.setParentId(toParent == null ? null : toParent.getId()); - if (id != null && storeWithRealm(realm).exists(id)) { - throw new ModelDuplicateException("Group exists: " + id); - } - entity = storeWithRealm(realm).create(entity); - - return entityToAdapterFunc(realm).apply(entity); - } - - @Override - public boolean removeGroup(RealmModel realm, GroupModel group) { - LOG.tracef("removeGroup(%s, %s)%s", realm, group, getShortStackTrace()); - if (group == null) return false; - - session.invalidate(GROUP_BEFORE_REMOVE, realm, group); - - storeWithRealm(realm).delete(group.getId()); - - session.invalidate(GROUP_AFTER_REMOVE, realm, group); - - return true; - } - - /* TODO: investigate following two methods, it seems they could be moved to model layer */ - - @Override - public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) { - LOG.tracef("moveGroup(%s, %s, %s)%s", realm, group, toParent, getShortStackTrace()); - - GroupModel previousParent = group.getParent(); - - if (toParent != null && group.getId().equals(toParent.getId())) { - return; - } - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.NAME, Operator.EQ, group.getName()); - - mcb = toParent == null ? - mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS) : - mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, toParent.getId()); - - try (Stream possibleSiblings = storeWithRealm(realm).read(withCriteria(mcb))) { - if (possibleSiblings.findAny().isPresent()) { - throw new ModelDuplicateException("Parent already contains subgroup named '" + group.getName() + "'"); - } - } - - if (group.getParentId() != null) { - group.getParent().removeChild(group); - } - group.setParent(toParent); - if (toParent != null) toParent.addChild(group); - - String newPath = KeycloakModelUtils.buildGroupPath(group); - String previousPath = KeycloakModelUtils.buildGroupPath(group, previousParent); - - GroupModel.GroupPathChangeEvent event = - new GroupModel.GroupPathChangeEvent() { - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public String getNewPath() { - return newPath; - } - - @Override - public String getPreviousPath() { - return previousPath; - } - - @Override - public KeycloakSession getKeycloakSession() { - return session; - } - }; - session.getKeycloakSessionFactory().publish(event); - } - - @Override - public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) { - LOG.tracef("addTopLevelGroup(%s, %s)%s", realm, subGroup, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.PARENT_ID, Operator.EQ, (Object) null) - .compare(SearchableFields.NAME, Operator.EQ, subGroup.getName()); - - try (Stream possibleSiblings = storeWithRealm(realm).read(withCriteria(mcb))) { - if (possibleSiblings.findAny().isPresent()) { - throw new ModelDuplicateException("There is already a top level group named '" + subGroup.getName() + "'"); - } - } - - subGroup.setParent(null); - } - - public void preRemove(RealmModel realm, RoleModel role) { - LOG.tracef("preRemove(%s, %s)%s", realm, role, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()); - try (Stream toRemove = storeWithRealm(realm).read(withCriteria(mcb))) { - toRemove - .map(groupEntity -> session.groups().getGroupById(realm, groupEntity.getId())) - .forEach(groupModel -> groupModel.deleteRoleMapping(role)); - } - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void close() { - } - - private Stream getGroupsByParentId(RealmModel realm, String parentId) { - LOG.tracef("getGroupsByParentId(%s)%s", parentId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb - .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.PARENT_ID, Operator.EQ, parentId); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)).map(entityToAdapterFunc(realm)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProviderFactory.java deleted file mode 100644 index cf9dff744ec..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProviderFactory.java +++ /dev/null @@ -1,80 +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.group; - -import java.util.stream.Collectors; -import org.keycloak.models.GroupModel; -import org.keycloak.models.GroupProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE; - -/** - * - * @author mhajas - */ -public class MapGroupProviderFactory extends AbstractMapProviderFactory implements GroupProviderFactory, InvalidationHandler { - - public MapGroupProviderFactory() { - super(GroupModel.class, MapGroupProvider.class); - } - - @Override - public MapGroupProvider createNew(KeycloakSession session) { - return new MapGroupProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "Group provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0]); - } else if (type == ROLE_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]); - } else if (type == GROUP_BEFORE_REMOVE) { - RealmModel realm = (RealmModel) params[0]; - GroupModel group = (GroupModel) params[1]; - - realm.removeDefaultGroup(group); - - // TODO: Should the batch size be a config option? - // batch and remove subgroups to avoid grinding server to a halt at scale - long batches = (long) Math.ceil(group.getSubGroupsCount() / 1000.0); - for(int i = 0; i < batches; i++) { - group.getSubGroupsStream(i * 1000, 1000) - .forEach(subGroup -> create(session).removeGroup(realm, subGroup)); - } - } else if (type == GROUP_AFTER_REMOVE) { - session.getKeycloakSessionFactory().publish(new GroupModel.GroupRemovedEvent() { - @Override public RealmModel getRealm() { return (RealmModel) params[0]; } - @Override public GroupModel getGroup() { return (GroupModel) params[1]; } - @Override public KeycloakSession getKeycloakSession() { return session; } - }); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/keys/MapPublicKeyStorageProvider.java b/model/map/src/main/java/org/keycloak/models/map/keys/MapPublicKeyStorageProvider.java deleted file mode 100644 index 9f229f31f93..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/keys/MapPublicKeyStorageProvider.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2022 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.keys; - -import org.jboss.logging.Logger; -import org.keycloak.crypto.KeyWrapper; -import org.keycloak.crypto.PublicKeysWrapper; -import org.keycloak.keys.PublicKeyLoader; -import org.keycloak.keys.PublicKeyStorageProvider; -import org.keycloak.models.KeycloakSession; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -public class MapPublicKeyStorageProvider implements PublicKeyStorageProvider { - - private static final Logger log = Logger.getLogger(MapPublicKeyStorageProvider.class); - - private final KeycloakSession session; - - private final Map> tasksInProgress; - - public MapPublicKeyStorageProvider(KeycloakSession session, Map> tasksInProgress) { - this.session = session; - this.tasksInProgress = tasksInProgress; - } - - @Override - public KeyWrapper getFirstPublicKey(String modelKey, String algorithm, PublicKeyLoader loader) { - return getPublicKey(modelKey, null, algorithm, loader); - } - - @Override - public KeyWrapper getPublicKey(String modelKey, String kid, String algorithm, PublicKeyLoader loader) { - WrapperCallable wrapperCallable = new WrapperCallable(modelKey, loader); - FutureTask task = new FutureTask<>(wrapperCallable); - FutureTask existing = tasksInProgress.putIfAbsent(modelKey, task); - PublicKeysWrapper currentKeys; - - if (existing == null) { - task.run(); - } else { - task = existing; - } - - try { - currentKeys = task.get(); - - // Computation finished. Let's see if key is available - KeyWrapper publicKey = currentKeys.getKeyByKidAndAlg(kid, algorithm); - if (publicKey != null) { - return publicKey; - } - - } catch (ExecutionException ee) { - throw new RuntimeException("Error when loading public keys: " + ee.getMessage(), ee); - } catch (InterruptedException ie) { - throw new RuntimeException("Error. Interrupted when loading public keys", ie); - } finally { - // Our thread inserted the task. Let's clean - if (existing == null) { - tasksInProgress.remove(modelKey); - } - } - - List availableKids = currentKeys == null ? Collections.emptyList() : currentKeys.getKids(); - log.warnf("PublicKey wasn't found in the storage. Requested kid: '%s' . Available kids: '%s'", kid, availableKids); - - return null; - } - - private class WrapperCallable implements Callable { - - private final String modelKey; - private final PublicKeyLoader delegate; - - public WrapperCallable(String modelKey, PublicKeyLoader delegate) { - this.modelKey = modelKey; - this.delegate = delegate; - } - - @Override - public PublicKeysWrapper call() throws Exception { - PublicKeysWrapper publicKeys = delegate.loadKeys(); - - if (log.isDebugEnabled()) { - log.debugf("Public keys retrieved successfully for model %s. New kids: %s", modelKey, publicKeys.getKids()); - } - - return publicKeys; - } - } - - @Override - public void close() { - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/keys/MapPublicKeyStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/keys/MapPublicKeyStorageProviderFactory.java deleted file mode 100644 index 5979e1771d0..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/keys/MapPublicKeyStorageProviderFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2022 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.keys; - -import org.keycloak.common.Profile; -import org.keycloak.crypto.PublicKeysWrapper; -import org.keycloak.keys.PublicKeyStorageProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.FutureTask; - -public class MapPublicKeyStorageProviderFactory extends AbstractMapProviderFactory - implements PublicKeyStorageProviderFactory, EnvironmentDependentProviderFactory { - - private final Map> tasksInProgress = new ConcurrentHashMap<>(); - - public MapPublicKeyStorageProviderFactory() { - super(Object.class, MapPublicKeyStorageProvider.class); - } - - @Override - public MapPublicKeyStorageProvider createNew(KeycloakSession session) { - return new MapPublicKeyStorageProvider(session, tasksInProgress); - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public String getHelpText() { - return "Public key storage provider"; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/lock/MapGlobalLockProvider.java b/model/map/src/main/java/org/keycloak/models/map/lock/MapGlobalLockProvider.java deleted file mode 100644 index 390a6a74fda..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/lock/MapGlobalLockProvider.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2023 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.lock; - -import org.keycloak.common.util.Retry; -import org.keycloak.common.util.Time; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionTaskWithResult; -import org.keycloak.models.locking.GlobalLockProvider; -import org.keycloak.models.locking.LockAcquiringTimeoutException; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; -import java.util.function.Supplier; - -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - - -/** - * Implementing a {@link GlobalLockProvider} based on a map storage. - * This requires the map store to support the entity type {@link MapLockEntity}. One of the stores which supports - * this is the JPA Map Store. The store needs to support the uniqueness of entries in the lock area, see - * {@link #lock(String)} for details. - * - * @author Alexander Schwartz - */ -public class MapGlobalLockProvider implements GlobalLockProvider { - - private final KeycloakSession session; - private final long defaultTimeoutMilliseconds; - private MapStorage store; - - /** - * The lockStoreSupplier allows the store to be initialized lazily and only when needed: As this provider is initialized - * for both the outer and the inner transactions, and the store is needed only for the inner transactions. - */ - private final Supplier> lockStoreSupplier; - - public MapGlobalLockProvider(KeycloakSession session, long defaultTimeoutMilliseconds, Supplier> lockStoreSupplier) { - this.defaultTimeoutMilliseconds = defaultTimeoutMilliseconds; - this.session = session; - this.lockStoreSupplier = lockStoreSupplier; - } - - @Override - public V withLock(String lockName, Duration timeToWaitForLock, KeycloakSessionTaskWithResult task) throws LockAcquiringTimeoutException { - MapLockEntity[] lockEntity = {null}; - try { - if (timeToWaitForLock == null) { - // Set default timeout if null provided - timeToWaitForLock = Duration.ofMillis(defaultTimeoutMilliseconds); - } - String[] keycloakInstanceIdentifier = {null}; - Instant[] timeWhenAcquired = {null}; - try { - Retry.executeWithBackoff(i -> lockEntity[0] = KeycloakModelUtils.runJobInTransactionWithResult(this.session.getKeycloakSessionFactory(), - innerSession -> { - MapGlobalLockProvider provider = (MapGlobalLockProvider) innerSession.getProvider(GlobalLockProvider.class); - // even if the call to provider.lock() succeeds, due to concurrency one can only be sure after a commit that all DB constraints have been met - return provider.lock(lockName); - }), (iteration, t) -> { - if (t instanceof LockAcquiringTimeoutException) { - LockAcquiringTimeoutException ex = (LockAcquiringTimeoutException) t; - keycloakInstanceIdentifier[0] = ex.getKeycloakInstanceIdentifier(); - timeWhenAcquired[0] = ex.getTimeWhenAcquired(); - } - }, timeToWaitForLock, 500); - } catch (RuntimeException ex) { - if (!(ex instanceof LockAcquiringTimeoutException)) { - throw new LockAcquiringTimeoutException(lockName, keycloakInstanceIdentifier[0], timeWhenAcquired[0], ex); - } - throw ex; - } - return KeycloakModelUtils.runJobInTransactionWithResult(this.session.getKeycloakSessionFactory(), task); - } finally { - if (lockEntity[0] != null) { - KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), innerSession -> { - MapGlobalLockProvider provider = (MapGlobalLockProvider) innerSession.getProvider(GlobalLockProvider.class); - provider.unlock(lockEntity[0]); - }); - } - } - } - - @Override - public void forceReleaseAllLocks() { - KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), innerSession -> { - MapGlobalLockProvider provider = (MapGlobalLockProvider) innerSession.getProvider(GlobalLockProvider.class); - provider.releaseAllLocks(); - }); - } - - @Override - public void close() { - } - - private void prepareTx() { - if (store == null) { - this.store = lockStoreSupplier.get(); - } - } - - /** - * Create a {@link MapLockEntity} for the provided lockName. - * The underlying store must ensure that a lock with the given name can be created only once in the store. - * This constraint needs to be checked either at the time of creation, or at the latest when the transaction - * is committed. If such a constraint violation is detected at the time of the transaction commit, it should - * throw an exception and the transaction should roll back. - *

- * The JPA Map Store implements this with a unique index, which is checked by the database both at the time of - * insertion and at the time the transaction is committed. - */ - private MapLockEntity lock(String lockName) { - prepareTx(); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(MapLockEntity.SearchableFields.NAME, ModelCriteriaBuilder.Operator.EQ, lockName); - Optional entry = store.read(QueryParameters.withCriteria(mcb)).findFirst(); - - if (entry.isEmpty()) { - MapLockEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapLockEntity.class); - entity.setName(lockName); - entity.setKeycloakInstanceIdentifier(getKeycloakInstanceIdentifier()); - entity.setTimeAcquired(Time.currentTimeMillis()); - return store.create(entity); - } else { - throw new LockAcquiringTimeoutException(lockName, entry.get().getKeycloakInstanceIdentifier(), Instant.ofEpochMilli(entry.get().getTimeAcquired())); - } - } - - /** - * Unlock the previously created lock. - * Will fail if the lock doesn't exist, or has a different owner. - */ - private void unlock(MapLockEntity lockEntity) { - prepareTx(); - MapLockEntity readLockEntity = store.read(lockEntity.getId()); - - if (readLockEntity == null) { - throw new RuntimeException("didn't find lock - someone else unlocked it?"); - } else if (!lockEntity.isLockUnchanged(readLockEntity)) { - // this case is there for stores which might re-use IDs or derive it from the name of the entity (like the file store map store does in some cases). - throw new RuntimeException(String.format("Lock owned by different instance: Lock [%s] acquired by keycloak instance [%s] at the time [%s]", - readLockEntity.getName(), readLockEntity.getKeycloakInstanceIdentifier(), readLockEntity.getTimeAcquired())); - } else { - store.delete(readLockEntity.getId()); - } - } - - private void releaseAllLocks() { - prepareTx(); - DefaultModelCriteria mcb = criteria(); - store.delete(QueryParameters.withCriteria(mcb)); - } - - private static String getKeycloakInstanceIdentifier() { - long pid = ProcessHandle.current().pid(); - String hostname; - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - hostname = "unknown-host"; - } - - String threadName = Thread.currentThread().getName(); - return threadName + "#" + pid + "@" + hostname; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/lock/MapGlobalLockProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/lock/MapGlobalLockProviderFactory.java deleted file mode 100644 index be47b4a925d..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/lock/MapGlobalLockProviderFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023 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.lock; - -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.locking.GlobalLockProvider; -import org.keycloak.models.locking.GlobalLockProviderFactory; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.provider.ProviderConfigurationBuilder; - -import java.util.List; - -/** - * Factory to create a GlobalLockProvider backed by a Map store. - * - * @author Alexander Schwartz - */ -public class MapGlobalLockProviderFactory extends AbstractMapProviderFactory implements GlobalLockProviderFactory, EnvironmentDependentProviderFactory { - - public static final String DEFAULT_TIMEOUT_MILLISECONDS = "defaultTimeoutMilliseconds"; - public static final long DEFAULT_VALUE = 5000L; - private long defaultTimeoutMilliseconds; - - public MapGlobalLockProviderFactory() { - super(MapLockEntity.class, GlobalLockProvider.class); - } - - @Override - public MapGlobalLockProvider createNew(KeycloakSession session) { - return new MapGlobalLockProvider(session, defaultTimeoutMilliseconds, () -> getMapStorage(session)); - } - - @Override - public void init(Config.Scope config) { - super.init(config); - defaultTimeoutMilliseconds = config.getLong(DEFAULT_TIMEOUT_MILLISECONDS, DEFAULT_VALUE); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } - - @Override - public String getHelpText() { - return "Lock provider"; - } - - @Override - public List getConfigMetadata() { - return ProviderConfigurationBuilder.create() - .property() - .name(DEFAULT_TIMEOUT_MILLISECONDS) - .type("int") - .helpText("Default timeout when waiting for a lock") - .defaultValue(DEFAULT_VALUE) - .add() - - .build(); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/lock/MapLockEntity.java b/model/map/src/main/java/org/keycloak/models/map/lock/MapLockEntity.java deleted file mode 100644 index 1016d98149e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/lock/MapLockEntity.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 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.lock; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.storage.SearchableModelField; - -import java.util.Objects; - -/** - * Entity to hold locks needed for the {@link MapGlobalLockProvider}. - * - * @author Alexander Schwartz - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.lock.MapLockEntity.AbstractLockEntity" -) -@DeepCloner.Root -public interface MapLockEntity extends UpdatableEntity, AbstractEntity { - - public static class SearchableFields { - public static final SearchableModelField NAME = new SearchableModelField<>("name", String.class); - } - - public abstract class AbstractLockEntity extends Impl implements MapLockEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - } - - String getName(); - void setName(String name); - - String getKeycloakInstanceIdentifier(); - void setKeycloakInstanceIdentifier(String keycloakInstanceIdentifier); - - Long getTimeAcquired(); - void setTimeAcquired(Long timeAcquired); - - default boolean isLockUnchanged(MapLockEntity otherMapLock) { - return Objects.equals(getKeycloakInstanceIdentifier(), otherMapLock.getKeycloakInstanceIdentifier()) && - Objects.equals(getTimeAcquired(), otherMapLock.getTimeAcquired()) && - Objects.equals(getName(), otherMapLock.getName()) && - Objects.equals(getId(), otherMapLock.getId()); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/loginFailure/AbstractUserLoginFailureModel.java b/model/map/src/main/java/org/keycloak/models/map/loginFailure/AbstractUserLoginFailureModel.java deleted file mode 100644 index 24a819fb139..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/loginFailure/AbstractUserLoginFailureModel.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.loginFailure; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -/** - * @author Martin Kanis - */ -public abstract class AbstractUserLoginFailureModel implements UserLoginFailureModel { - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractUserLoginFailureModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof UserLoginFailureModel)) return false; - - MapUserLoginFailureAdapter that = (MapUserLoginFailureAdapter) o; - return Objects.equals(that.entity.getId(), entity.getId()); - } - - @Override - public int hashCode() { - return entity.getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureAdapter.java b/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureAdapter.java deleted file mode 100644 index 3b9d75d51db..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureAdapter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.loginFailure; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.TimeAdapter; - -/** - * @author Martin Kanis - */ -public class MapUserLoginFailureAdapter extends AbstractUserLoginFailureModel { - public MapUserLoginFailureAdapter(KeycloakSession session, RealmModel realm, MapUserLoginFailureEntity entity) { - super(session, realm, entity); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getUserId() { - return entity.getUserId(); - } - - @Override - public int getFailedLoginNotBefore() { - Long failedLoginNotBefore = entity.getFailedLoginNotBefore(); - return failedLoginNotBefore == null ? 0 : TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(failedLoginNotBefore); - } - - @Override - public void setFailedLoginNotBefore(int notBefore) { - entity.setFailedLoginNotBefore(TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(notBefore)); - } - - @Override - public int getNumFailures() { - Integer numFailures = entity.getNumFailures(); - return numFailures == null ? 0 : numFailures; - } - - @Override - public void incrementFailures() { - entity.setNumFailures(getNumFailures() + 1); - } - - @Override - public void clearFailures() { - entity.clearFailures(); - } - - @Override - public long getLastFailure() { - Long lastFailure = entity.getLastFailure(); - return lastFailure == null ? 0l : lastFailure; - } - - @Override - public void setLastFailure(long lastFailure) { - entity.setLastFailure(lastFailure); - } - - @Override - public String getLastIPFailure() { - return entity.getLastIPFailure(); - } - - @Override - public void setLastIPFailure(String ip) { - entity.setLastIPFailure(ip); - } - - @Override - public String toString() { - return String.format("%s@%08x", entity.getId(), hashCode()); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureEntity.java b/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureEntity.java deleted file mode 100644 index 96f2a504d59..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureEntity.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.loginFailure; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.common.AbstractEntity; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -/** - * @author Martin Kanis - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity.AbstractUserLoginFailureEntity" -) -@DeepCloner.Root -public interface MapUserLoginFailureEntity extends AbstractEntity, UpdatableEntity { - - public abstract class AbstractUserLoginFailureEntity extends UpdatableEntity.Impl implements MapUserLoginFailureEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - @Override - public void clearFailures() { - this.updated |= getFailedLoginNotBefore() != null || getNumFailures() != null || getLastFailure() != null || getLastIPFailure() != null; - setFailedLoginNotBefore(null); - setNumFailures(null); - setLastFailure(null); - setLastIPFailure(null); - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getUserId(); - void setUserId(String userId); - - Long getFailedLoginNotBefore(); - void setFailedLoginNotBefore(Long failedLoginNotBefore); - - Integer getNumFailures(); - void setNumFailures(Integer numFailures); - - Long getLastFailure(); - void setLastFailure(Long lastFailure); - - String getLastIPFailure(); - void setLastIPFailure(String lastIPFailure); - - @IgnoreForEntityImplementationGenerator - void clearFailures(); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java b/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java deleted file mode 100644 index 16bf8350573..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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.loginFailure; - -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.UserLoginFailureProvider; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.MapStorage; - -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import java.util.function.Function; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -/** - * @author Martin Kanis - */ -public class MapUserLoginFailureProvider implements UserLoginFailureProvider { - - private static final Logger LOG = Logger.getLogger(MapUserLoginFailureProvider.class); - private final KeycloakSession session; - protected final MapStorage store; - private final boolean storeHasRealmId; - - public MapUserLoginFailureProvider(KeycloakSession session, MapStorage userLoginFailureStore) { - this.session = session; - - this.store = userLoginFailureStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private Function userLoginFailureEntityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return origEntity -> new MapUserLoginFailureAdapter(session, realm, origEntity); - } - - @Override - public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserLoginFailureModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(UserLoginFailureModel.SearchableFields.USER_ID, Operator.EQ, userId); - - LOG.tracef("getUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .findFirst() - .map(userLoginFailureEntityToAdapterFunc(realm)) - .orElse(null); - } - - @Override - public UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserLoginFailureModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(UserLoginFailureModel.SearchableFields.USER_ID, Operator.EQ, userId); - - LOG.tracef("addUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); - - MapUserLoginFailureEntity userLoginFailureEntity = storeWithRealm(realm).read(withCriteria(mcb)).findFirst().orElse(null); - - if (userLoginFailureEntity == null) { - userLoginFailureEntity = DeepCloner.DUMB_CLONER.newInstance(MapUserLoginFailureEntity.class); - userLoginFailureEntity.setRealmId(realm.getId()); - userLoginFailureEntity.setUserId(userId); - - userLoginFailureEntity = storeWithRealm(realm).create(userLoginFailureEntity); - } - - return userLoginFailureEntityToAdapterFunc(realm).apply(userLoginFailureEntity); - } - - @Override - public void removeUserLoginFailure(RealmModel realm, String userId) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserLoginFailureModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(UserLoginFailureModel.SearchableFields.USER_ID, Operator.EQ, userId); - - LOG.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void removeAllUserLoginFailures(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserLoginFailureModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - LOG.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void close() { - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProviderFactory.java deleted file mode 100644 index c4ad13ed3d4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProviderFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.loginFailure; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserLoginFailureProviderFactory; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_BEFORE_REMOVE; - -/** - * @author Martin Kanis - */ -public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFactory - implements UserLoginFailureProviderFactory, InvalidationHandler { - - public MapUserLoginFailureProviderFactory() { - super(UserLoginFailureModel.class, MapUserLoginFailureProvider.class); - } - - @Override - public MapUserLoginFailureProvider createNew(KeycloakSession session) { - return new MapUserLoginFailureProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "User login failure provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).removeAllUserLoginFailures((RealmModel) params[0]); - } else if (type == USER_BEFORE_REMOVE) { - create(session).removeUserLoginFailure((RealmModel) params[0], ((UserModel) params[1]).getId()); - } - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/AbstractRealmModel.java b/model/map/src/main/java/org/keycloak/models/map/realm/AbstractRealmModel.java deleted file mode 100644 index de5a15acf90..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/AbstractRealmModel.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.realm; - -import java.util.Objects; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractEntity; - -public abstract class AbstractRealmModel implements RealmModel { - - protected final KeycloakSession session; - protected final E entity; - - public AbstractRealmModel(KeycloakSession session, E entity) { - Objects.requireNonNull(entity, "entity"); - - this.session = session; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RealmModel)) return false; - - RealmModel that = (RealmModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java deleted file mode 100644 index 8c9c1a41dec..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java +++ /dev/null @@ -1,1767 +0,0 @@ -/* - * 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.realm; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import static java.util.Objects.nonNull; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.broker.provider.IdentityProvider; -import org.keycloak.broker.provider.IdentityProviderFactory; -import org.keycloak.broker.social.SocialIdentityProvider; -import org.keycloak.common.enums.SslRequired; -import org.keycloak.component.ComponentFactory; -import org.keycloak.component.ComponentModel; -import org.keycloak.component.ComponentValidationException; -import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.AuthenticatorConfigModel; -import org.keycloak.models.CibaConfig; -import org.keycloak.models.ClientInitialAccessModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.IdentityProviderMapperModel; -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.OAuth2DeviceConfig; -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.ParConfig; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.WebAuthnPolicy; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapComponentEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -import org.keycloak.models.utils.ComponentUtil; - -public class MapRealmAdapter extends AbstractRealmModel implements RealmModel { - - private static final Logger LOG = Logger.getLogger(MapRealmAdapter.class); - private static final String ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN = "actionTokenGeneratedByUserLifespan"; - private static final String DEFAULT_SIGNATURE_ALGORITHM = "defaultSignatureAlgorithm"; - private static final String BRUTE_FORCE_PROTECTED = "bruteForceProtected"; - private static final String PERMANENT_LOCKOUT = "permanentLockout"; - private static final String MAX_FAILURE_WAIT_SECONDS = "maxFailureWaitSeconds"; - private static final String WAIT_INCREMENT_SECONDS = "waitIncrementSeconds"; - private static final String QUICK_LOGIN_CHECK_MILLISECONDS = "quickLoginCheckMilliSeconds"; - private static final String MINIMUM_QUICK_LOGIN_WAIT_SECONDS = "minimumQuickLoginWaitSeconds"; - private static final String MAX_DELTA_SECONDS = "maxDeltaTimeSeconds"; - private static final String FAILURE_FACTOR = "failureFactor"; - - private PasswordPolicy passwordPolicy; - - public MapRealmAdapter(KeycloakSession session, MapRealmEntity entity) { - super(session, entity); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - entity.setName(name); - } - - @Override - public String getDisplayName() { - return entity.getDisplayName(); - } - - @Override - public void setDisplayName(String displayName) { - entity.setDisplayName(displayName); - } - - @Override - public String getDisplayNameHtml() { - return entity.getDisplayNameHtml(); - } - - @Override - public void setDisplayNameHtml(String displayNameHtml) { - entity.setDisplayNameHtml(displayNameHtml); - } - - @Override - public boolean isEnabled() { - Boolean enabled = entity.isEnabled(); - return enabled == null ? false : enabled; - } - - @Override - public void setEnabled(boolean enabled) { - entity.setEnabled(enabled); - } - - @Override - public SslRequired getSslRequired() { - String sslRequired = entity.getSslRequired(); - return sslRequired == null ? null : SslRequired.valueOf(sslRequired); - } - - @Override - public void setSslRequired(SslRequired sslRequired) { - entity.setSslRequired(sslRequired.name()); - } - - @Override - public boolean isRegistrationAllowed() { - Boolean is = entity.isRegistrationAllowed(); - return is == null ? false : is; - } - - @Override - public void setRegistrationAllowed(boolean registrationAllowed) { - entity.setRegistrationAllowed(registrationAllowed); - } - - @Override - public boolean isRegistrationEmailAsUsername() { - Boolean is = entity.isRegistrationEmailAsUsername(); - return is == null ? false : is; - } - - @Override - public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) { - entity.setRegistrationEmailAsUsername(registrationEmailAsUsername); - } - - @Override - public boolean isRememberMe() { - Boolean is = entity.isRememberMe(); - return is == null ? false : is; - } - - @Override - public void setRememberMe(boolean rememberMe) { - entity.setRememberMe(rememberMe); - } - - @Override - public boolean isEditUsernameAllowed() { - Boolean is = entity.isEditUsernameAllowed(); - return is == null ? false : is; - } - - @Override - public void setEditUsernameAllowed(boolean editUsernameAllowed) { - entity.setEditUsernameAllowed(editUsernameAllowed); - } - - @Override - public boolean isUserManagedAccessAllowed() { - Boolean is = entity.isAllowUserManagedAccess(); - return is == null ? false : is; - } - - @Override - public void setUserManagedAccessAllowed(boolean userManagedAccessAllowed) { - entity.setAllowUserManagedAccess(userManagedAccessAllowed); - } - - @Override - public void setAttribute(String name, String value) { - entity.setAttribute(name, Collections.singletonList(value)); - } - - @Override - public void removeAttribute(String name) { - entity.removeAttribute(name); - } - - @Override - public String getAttribute(String name) { - List attribute = entity.getAttribute(name); - if (attribute == null || attribute.isEmpty()) return null; - return attribute.get(0); - } - - @Override - public Map getAttributes() { - Map> attrs = entity.getAttributes(); - - return attrs == null || attrs.isEmpty() ? Collections.emptyMap() : attrs.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, - entry -> { - if (entry.getValue().isEmpty()) { - return null; - } else if (entry.getValue().size() > 1) { - // This could be caused by an inconsistency in the storage, a programming error, - // or a downgrade from a future version of Keycloak that already supports multi-valued attributes. - // The caller will not see the other values, and when this entity is later updated, the additional values be will lost. - LOG.warnf("Realm '%s' has attribute '%s' with %d values, retrieving only the first", getId(), entry.getKey(), - entry.getValue().size()); - } - return entry.getValue().get(0); - }) - ); - } - - @Override - public boolean isVerifyEmail() { - Boolean is = entity.isVerifyEmail(); - return is == null ? false : is; - } - - @Override - public void setVerifyEmail(boolean verifyEmail) { - entity.setVerifyEmail(verifyEmail); - } - - @Override - public boolean isLoginWithEmailAllowed() { - Boolean is = entity.isLoginWithEmailAllowed(); - return is == null ? false : is; - } - - @Override - public void setLoginWithEmailAllowed(boolean loginWithEmailAllowed) { - entity.setLoginWithEmailAllowed(loginWithEmailAllowed); - } - - @Override - public boolean isDuplicateEmailsAllowed() { - Boolean is = entity.isDuplicateEmailsAllowed(); - return is == null ? false : is; - } - - @Override - public void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed) { - entity.setDuplicateEmailsAllowed(duplicateEmailsAllowed); - } - - @Override - public boolean isResetPasswordAllowed() { - Boolean is = entity.isResetPasswordAllowed(); - return is == null ? false : is; - } - - @Override - public void setResetPasswordAllowed(boolean resetPasswordAllowed) { - entity.setResetPasswordAllowed(resetPasswordAllowed); - } - - @Override - public boolean isRevokeRefreshToken() { - Boolean is = entity.isRevokeRefreshToken(); - return is == null ? false : is; - } - - @Override - public void setRevokeRefreshToken(boolean revokeRefreshToken) { - entity.setRevokeRefreshToken(revokeRefreshToken); - } - - @Override - public int getRefreshTokenMaxReuse() { - Integer i = entity.getRefreshTokenMaxReuse(); - return i == null ? 0 : i; - } - - @Override - public void setRefreshTokenMaxReuse(int revokeRefreshTokenCount) { - entity.setRefreshTokenMaxReuse(revokeRefreshTokenCount); - } - - @Override - public int getSsoSessionIdleTimeout() { - Integer i = entity.getSsoSessionIdleTimeout(); - return i == null ? 0 : i; - } - - @Override - public void setSsoSessionIdleTimeout(int seconds) { - entity.setSsoSessionIdleTimeout(seconds); - } - - @Override - public int getSsoSessionMaxLifespan() { - Integer i = entity.getSsoSessionMaxLifespan(); - return i == null ? 0 : i; - } - - @Override - public void setSsoSessionMaxLifespan(int seconds) { - entity.setSsoSessionMaxLifespan(seconds); - } - - @Override - public int getSsoSessionIdleTimeoutRememberMe() { - Integer i = entity.getSsoSessionIdleTimeoutRememberMe(); - return i == null ? 0 : i; - } - - @Override - public void setSsoSessionIdleTimeoutRememberMe(int seconds) { - entity.setSsoSessionIdleTimeoutRememberMe(seconds); - } - - @Override - public int getSsoSessionMaxLifespanRememberMe() { - Integer i = entity.getSsoSessionMaxLifespanRememberMe(); - return i == null ? 0 : i; - } - - @Override - public void setSsoSessionMaxLifespanRememberMe(int seconds) { - entity.setSsoSessionMaxLifespanRememberMe(seconds); - } - - @Override - public int getOfflineSessionIdleTimeout() { - Integer i = entity.getOfflineSessionIdleTimeout(); - return i == null ? 0 : i; - } - - @Override - public void setOfflineSessionIdleTimeout(int seconds) { - entity.setOfflineSessionIdleTimeout(seconds); - } - - @Override - public int getAccessTokenLifespan() { - Integer i = entity.getAccessTokenLifespan(); - return i == null ? 0 : i; - } - - @Override - public int getClientSessionIdleTimeout() { - Integer i = entity.getClientSessionIdleTimeout(); - return i == null ? 0 : i; - } - - @Override - public void setClientSessionIdleTimeout(int seconds) { - entity.setClientSessionIdleTimeout(seconds); - } - - @Override - public int getClientSessionMaxLifespan() { - Integer i = entity.getClientSessionMaxLifespan(); - return i == null ? 0 : i; - } - - @Override - public void setClientSessionMaxLifespan(int seconds) { - entity.setClientSessionMaxLifespan(seconds); - } - - @Override - public int getClientOfflineSessionIdleTimeout() { - Integer i = entity.getClientOfflineSessionIdleTimeout(); - return i == null ? 0 : i; - } - - @Override - public void setClientOfflineSessionIdleTimeout(int seconds) { - entity.setClientOfflineSessionIdleTimeout(seconds); - } - - @Override - public int getClientOfflineSessionMaxLifespan() { - Integer i = entity.getClientOfflineSessionMaxLifespan(); - return i == null ? 0 : i; - } - - @Override - public void setClientOfflineSessionMaxLifespan(int seconds) { - entity.setClientOfflineSessionMaxLifespan(seconds); - } - - @Override - public void setAccessTokenLifespan(int seconds) { - entity.setAccessTokenLifespan(seconds); - } - - @Override - public int getAccessTokenLifespanForImplicitFlow() { - Integer i = entity.getAccessTokenLifespanForImplicitFlow(); - return i == null ? 0 : i; - } - - @Override - public void setAccessTokenLifespanForImplicitFlow(int seconds) { - entity.setAccessTokenLifespanForImplicitFlow(seconds); - } - - @Override - public int getAccessCodeLifespan() { - Integer i = entity.getAccessCodeLifespan(); - return i == null ? 0 : i; - } - - @Override - public void setAccessCodeLifespan(int seconds) { - entity.setAccessCodeLifespan(seconds); - } - - @Override - public int getAccessCodeLifespanUserAction() { - Integer i = entity.getAccessCodeLifespanUserAction(); - return i == null ? 0 : i; - } - - @Override - public void setAccessCodeLifespanUserAction(int seconds) { - entity.setAccessCodeLifespanUserAction(seconds); - } - - @Override - public int getAccessCodeLifespanLogin() { - Integer i = entity.getAccessCodeLifespanLogin(); - return i == null ? 0 : i; - } - - @Override - public void setAccessCodeLifespanLogin(int seconds) { - entity.setAccessCodeLifespanLogin(seconds); - } - - @Override - public int getActionTokenGeneratedByAdminLifespan() { - Integer i = entity.getActionTokenGeneratedByAdminLifespan(); - return i == null ? 0 : i; - } - - @Override - public void setActionTokenGeneratedByAdminLifespan(int seconds) { - entity.setActionTokenGeneratedByAdminLifespan(seconds); - } - - @Override - public int getActionTokenGeneratedByUserLifespan() { - return getAttribute(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN, getAccessCodeLifespanUserAction()); - } - - @Override - public void setActionTokenGeneratedByUserLifespan(int seconds) { - setAttribute(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN, seconds); - } - - @Override - public int getActionTokenGeneratedByUserLifespan(String actionTokenType) { - if (actionTokenType == null || getAttribute(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + "." + actionTokenType) == null) { - return getActionTokenGeneratedByUserLifespan(); - } - return getAttribute(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + "." + actionTokenType, getAccessCodeLifespanUserAction()); - } - - @Override - public void setActionTokenGeneratedByUserLifespan(String actionTokenType, Integer seconds) { - if (actionTokenType != null && ! actionTokenType.isEmpty() && seconds != null) { - setAttribute(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + "." + actionTokenType, seconds); - } - } - - @Override - public Map getUserActionTokenLifespans() { - Map> attrs = entity.getAttributes(); - if (attrs == null || attrs.isEmpty()) return Collections.emptyMap(); - - Map tokenLifespans = attrs.entrySet().stream() - .filter(Objects::nonNull) - .filter(entry -> nonNull(entry.getValue()) && ! entry.getValue().isEmpty()) - .filter(entry -> entry.getKey().startsWith(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + ".")) - .collect(Collectors.toMap( - entry -> entry.getKey().substring(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN.length() + 1), - entry -> Integer.valueOf(entry.getValue().get(0)))); - - return Collections.unmodifiableMap(tokenLifespans); - } - - @Override - public Stream getRequiredCredentialsStream() { - Set rCEs = entity.getRequiredCredentials(); - return rCEs == null ? Stream.empty() : rCEs.stream().map(MapRequiredCredentialEntity::toModel); - } - - @Override - public void addRequiredCredential(String cred) { - RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(cred); - if (model == null) { - throw new RuntimeException("Unknown credential type " + cred); - } - if (getRequiredCredentialsStream().anyMatch(credential -> Objects.equals(model.getType(), credential.getType()))) { - throw new ModelDuplicateException("A Required Credential with given type already exists."); - } - entity.addRequiredCredential(MapRequiredCredentialEntity.fromModel(model)); - } - - @Override - public void updateRequiredCredentials(Set credentials) { - Set requiredCredentialEntities = entity.getRequiredCredentials(); - Consumer updateCredentialFnc = e -> { - Optional existingEntity = requiredCredentialEntities.stream() - .filter(existing -> Objects.equals(e.getType(), existing.getType())) - .findFirst(); - - if (existingEntity.isPresent()) { - updateRequiredCredential(existingEntity.get(), e); - } else { - entity.addRequiredCredential(e); - } - }; - - credentials.stream() - .map(RequiredCredentialModel.BUILT_IN::get) - .peek(c -> { if (c == null) throw new RuntimeException("Unknown credential type " + c.getType()); }) - .map(MapRequiredCredentialEntity::fromModel) - .forEach(updateCredentialFnc); - } - - private void updateRequiredCredential(MapRequiredCredentialEntity existing, MapRequiredCredentialEntity newValue) { - existing.setFormLabel(newValue.getFormLabel()); - existing.setInput(newValue.isInput()); - existing.setSecret(newValue.isSecret()); - } - - @Override - public PasswordPolicy getPasswordPolicy() { - if (passwordPolicy == null) { - passwordPolicy = PasswordPolicy.parse(session, entity.getPasswordPolicy()); - } - return passwordPolicy; - } - - @Override - public void setPasswordPolicy(PasswordPolicy policy) { - this.passwordPolicy = policy; - entity.setPasswordPolicy(policy.toString()); - } - - @Override - public OTPPolicy getOTPPolicy() { - MapOTPPolicyEntity policy = entity.getOTPPolicy(); - return policy == null ? OTPPolicy.DEFAULT_POLICY : MapOTPPolicyEntity.toModel(policy); - } - - @Override - public void setOTPPolicy(OTPPolicy policy) { - entity.setOTPPolicy(MapOTPPolicyEntity.fromModel(policy)); - } - - @Override - public RoleModel getRoleById(String id) { - return session.roles().getRoleById(this, id); - } - - @Override - public Stream getDefaultGroupsStream() { - Set gIds = entity.getDefaultGroupIds(); - return gIds == null ? Stream.empty() : gIds.stream().map(this::getGroupById); - } - - @Override - public void addDefaultGroup(GroupModel group) { - entity.addDefaultGroupId(group.getId()); - } - - @Override - public void removeDefaultGroup(GroupModel group) { - entity.removeDefaultGroupId(group.getId()); - } - - @Override - public Stream getClientsStream() { - return session.clients().getClientsStream(this); - } - - @Override - public Stream getClientsStream(Integer firstResult, Integer maxResults) { - return session.clients().getClientsStream(this, firstResult, maxResults); - } - - @Override - public Long getClientsCount() { - return session.clients().getClientsCount(this); - } - - @Override - public Stream getAlwaysDisplayInConsoleClientsStream() { - return session.clients().getAlwaysDisplayInConsoleClientsStream(this); - } - - @Override - public ClientModel addClient(String name) { - return session.clients().addClient(this, name); - } - - @Override - public ClientModel addClient(String id, String clientId) { - return session.clients().addClient(this, id, clientId); - } - - @Override - public boolean removeClient(String id) { - return session.clients().removeClient(this, id); - } - - @Override - public ClientModel getClientById(String id) { - return session.clients().getClientById(this, id); - } - - @Override - public ClientModel getClientByClientId(String clientId) { - return session.clients().getClientByClientId(this, clientId); - } - - @Override - public Stream searchClientByClientIdStream(String clientId, Integer firstResult, Integer maxResults) { - return session.clients().searchClientsByClientIdStream(this, clientId, firstResult, maxResults); - } - - @Override - public Stream searchClientByAttributes(Map attributes, Integer firstResult, Integer maxResults) { - return session.clients().searchClientsByAttributes(this, attributes, firstResult, maxResults); - } - - @Override - public Map getSmtpConfig() { - Map sC = entity.getSmtpConfig(); - return sC == null ? Collections.emptyMap() : Collections.unmodifiableMap(sC); - } - - @Override - public void setSmtpConfig(Map smtpConfig) { - entity.setSmtpConfig(smtpConfig); - } - - @Override - public AuthenticationFlowModel getBrowserFlow() { - return getAuthenticationFlowById(entity.getBrowserFlow()); - } - - @Override - public void setBrowserFlow(AuthenticationFlowModel flow) { - entity.setBrowserFlow(flow.getId()); - } - - @Override - public AuthenticationFlowModel getRegistrationFlow() { - return getAuthenticationFlowById(entity.getRegistrationFlow()); - } - - @Override - public void setRegistrationFlow(AuthenticationFlowModel flow) { - entity.setRegistrationFlow(flow.getId()); - } - - @Override - public AuthenticationFlowModel getDirectGrantFlow() { - return getAuthenticationFlowById(entity.getDirectGrantFlow()); - } - - @Override - public void setDirectGrantFlow(AuthenticationFlowModel flow) { - entity.setDirectGrantFlow(flow.getId()); - } - - @Override - public AuthenticationFlowModel getResetCredentialsFlow() { - return getAuthenticationFlowById(entity.getResetCredentialsFlow()); - } - - @Override - public void setResetCredentialsFlow(AuthenticationFlowModel flow) { - entity.setResetCredentialsFlow(flow.getId()); - } - - @Override - public AuthenticationFlowModel getClientAuthenticationFlow() { - return getAuthenticationFlowById(entity.getClientAuthenticationFlow()); - } - - @Override - public void setClientAuthenticationFlow(AuthenticationFlowModel flow) { - entity.setClientAuthenticationFlow(flow.getId()); - } - - @Override - public AuthenticationFlowModel getDockerAuthenticationFlow() { - return getAuthenticationFlowById(entity.getDockerAuthenticationFlow()); - } - - @Override - public void setDockerAuthenticationFlow(AuthenticationFlowModel flow) { - entity.setDockerAuthenticationFlow(flow.getId()); - } - - @Override - public Stream getAuthenticationFlowsStream() { - Set afs = entity.getAuthenticationFlows(); - return afs == null ? Stream.empty() : afs.stream().map(MapAuthenticationFlowEntity::toModel); - } - - @Override - public AuthenticationFlowModel getFlowByAlias(String alias) { - Set afs = entity.getAuthenticationFlows(); - return afs == null ? null : afs.stream() - .filter(flow -> Objects.equals(flow.getAlias(), alias)) - .findFirst() - .map(MapAuthenticationFlowEntity::toModel) - .orElse(null); - } - - @Override - public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) { - if (entity.getAuthenticationFlow(model.getId()).isPresent()) { - throw new ModelDuplicateException("An AuthenticationFlow with given id already exists"); - } - - MapAuthenticationFlowEntity authenticationFlowEntity = MapAuthenticationFlowEntity.fromModel(model); - entity.addAuthenticationFlow(authenticationFlowEntity); - - return MapAuthenticationFlowEntity.toModel(authenticationFlowEntity); - } - - @Override - public AuthenticationFlowModel getAuthenticationFlowById(String flowId) { - if (flowId == null) return null; - return entity.getAuthenticationFlow(flowId).map(MapAuthenticationFlowEntity::toModel).orElse(null); - } - - @Override - public void removeAuthenticationFlow(AuthenticationFlowModel model) { - entity.removeAuthenticationFlow(model.getId()); - } - - @Override - public void updateAuthenticationFlow(AuthenticationFlowModel model) { - entity.getAuthenticationFlow(model.getId()) - .ifPresent(existing -> { - existing.setAlias(model.getAlias()); - existing.setDescription(model.getDescription()); - existing.setProviderId(model.getProviderId()); - existing.setBuiltIn(model.isBuiltIn()); - existing.setTopLevel(model.isTopLevel()); - }); - } - - @Override - public Stream getAuthenticationExecutionsStream(String flowId) { - Set aee = entity.getAuthenticationExecutions(); - return aee == null ? Stream.empty() : aee.stream() - .filter(execution -> Objects.equals(flowId, execution.getParentFlowId())) - .map(MapAuthenticationExecutionEntity::toModel) - .sorted(AuthenticationExecutionModel.ExecutionComparator.SINGLETON); - } - - @Override - public AuthenticationExecutionModel getAuthenticationExecutionById(String id) { - if (id == null) return null; - return entity.getAuthenticationExecution(id).map(MapAuthenticationExecutionEntity::toModel).orElse(null); - } - - @Override - public AuthenticationExecutionModel getAuthenticationExecutionByFlowId(String flowId) { - Set aee = entity.getAuthenticationExecutions(); - return aee == null ? null : aee.stream() - .filter(execution -> Objects.equals(flowId, execution.getFlowId())) - .findAny() - .map(MapAuthenticationExecutionEntity::toModel) - .orElse(null); - } - - @Override - public AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model) { - if (entity.getAuthenticationExecution(model.getId()).isPresent()) { - throw new ModelDuplicateException("An RequiredActionProvider with given id already exists"); - } - MapAuthenticationExecutionEntity executionEntity = MapAuthenticationExecutionEntity.fromModel(model); - entity.addAuthenticationExecution(executionEntity); - return MapAuthenticationExecutionEntity.toModel(executionEntity); - } - - @Override - public void updateAuthenticatorExecution(AuthenticationExecutionModel model) { - entity.getAuthenticationExecution(model.getId()) - .ifPresent(existing -> { - existing.setAuthenticator(model.getAuthenticator()); - existing.setAuthenticatorConfig(model.getAuthenticatorConfig()); - existing.setFlowId(model.getFlowId()); - existing.setParentFlowId(model.getParentFlow()); - existing.setRequirement(model.getRequirement()); - existing.setAutheticatorFlow(model.isAuthenticatorFlow()); - existing.setPriority(model.getPriority()); - }); - } - - @Override - public void removeAuthenticatorExecution(AuthenticationExecutionModel model) { - entity.removeAuthenticationExecution(model.getId()); - } - - @Override - public Stream getAuthenticatorConfigsStream() { - Set acs = entity.getAuthenticatorConfigs(); - return acs == null ? Stream.empty() : acs.stream().map(MapAuthenticatorConfigEntity::toModel); - } - - @Override - public AuthenticatorConfigModel addAuthenticatorConfig(AuthenticatorConfigModel model) { - if (entity.getAuthenticatorConfig(model.getId()).isPresent()) { - throw new ModelDuplicateException("An Authenticator Config with given id already exists."); - } - MapAuthenticatorConfigEntity authenticatorConfig = MapAuthenticatorConfigEntity.fromModel(model); - entity.addAuthenticatorConfig(authenticatorConfig); - model.setId(authenticatorConfig.getId()); - return model; - } - - @Override - public void updateAuthenticatorConfig(AuthenticatorConfigModel model) { - entity.getAuthenticatorConfig(model.getId()) - .ifPresent(oldAC -> { - oldAC.setAlias(model.getAlias()); - oldAC.setConfig(model.getConfig()); - }); - } - - @Override - public void removeAuthenticatorConfig(AuthenticatorConfigModel model) { - entity.removeAuthenticatorConfig(model.getId()); - } - - @Override - public AuthenticatorConfigModel getAuthenticatorConfigById(String id) { - if (id == null) return null; - return entity.getAuthenticatorConfig(id).map(MapAuthenticatorConfigEntity::toModel).orElse(null); - } - - @Override - public AuthenticatorConfigModel getAuthenticatorConfigByAlias(String alias) { - Set acs = entity.getAuthenticatorConfigs(); - return acs == null ? null : acs.stream() - .filter(config -> Objects.equals(config.getAlias(), alias)) - .findFirst() - .map(MapAuthenticatorConfigEntity::toModel) - .orElse(null); - } - - @Override - public Stream getRequiredActionProvidersStream() { - Set raps = entity.getRequiredActionProviders(); - return raps == null ? Stream.empty() : raps.stream() - .map(MapRequiredActionProviderEntity::toModel) - .sorted(RequiredActionProviderModel.RequiredActionComparator.SINGLETON); - } - - @Override - public RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model) { - if (entity.getRequiredActionProvider(model.getId()).isPresent()) { - throw new ModelDuplicateException("A Required Action Provider with given id already exists."); - } - if (getRequiredActionProviderByAlias(model.getAlias()) != null) { - throw new ModelDuplicateException("A Required Action Provider with given alias already exists."); - } - MapRequiredActionProviderEntity requiredActionProvider = MapRequiredActionProviderEntity.fromModel(model); - entity.addRequiredActionProvider(requiredActionProvider); - - return MapRequiredActionProviderEntity.toModel(requiredActionProvider); - } - - @Override - public void updateRequiredActionProvider(RequiredActionProviderModel model) { - entity.getRequiredActionProvider(model.getId()) - .ifPresent(oldRAP -> { - oldRAP.setAlias(model.getAlias()); - oldRAP.setName(model.getName()); - oldRAP.setProviderId(model.getProviderId()); - oldRAP.setPriority(model.getPriority()); - oldRAP.setEnabled(model.isEnabled()); - oldRAP.setDefaultAction(model.isDefaultAction()); - oldRAP.setConfig(model.getConfig()); - }); - } - - @Override - public void removeRequiredActionProvider(RequiredActionProviderModel model) { - entity.removeRequiredActionProvider(model.getId()); - } - - @Override - public RequiredActionProviderModel getRequiredActionProviderById(String id) { - if (id == null) return null; - - return entity.getRequiredActionProvider(id).map(MapRequiredActionProviderEntity::toModel).orElse(null); - } - - @Override - public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { - Set raps = entity.getRequiredActionProviders(); - return raps == null ? null : raps.stream() - .filter(actionProvider -> Objects.equals(actionProvider.getAlias(), alias)) - .findFirst() - .map(MapRequiredActionProviderEntity::toModel) - .orElse(null); - } - - @Override - public Stream getIdentityProvidersStream() { - Set ips = entity.getIdentityProviders(); - return ips == null ? Stream.empty() : ips.stream() - .map(e -> MapIdentityProviderEntity.toModel(e, () -> this.getModelFromProviderFactory(e.getProviderId()))); - } - - @Override - public IdentityProviderModel getIdentityProviderByAlias(String alias) { - Set ips = entity.getIdentityProviders(); - return ips == null ? null : ips.stream() - .filter(identityProvider -> Objects.equals(identityProvider.getAlias(), alias)) - .findFirst() - .map(e -> MapIdentityProviderEntity.toModel(e, () -> this.getModelFromProviderFactory(e.getProviderId()))) - .orElse(null); - } - - // This is a violation of layering requirements, this should NOT be in store code. - // However, there is no easy way around this given the current number of IdentityProviderModel implementations - private IdentityProviderModel getModelFromProviderFactory(String providerId) { - Optional factory = Stream.concat(session.getKeycloakSessionFactory().getProviderFactoriesStream(IdentityProvider.class), - session.getKeycloakSessionFactory().getProviderFactoriesStream(SocialIdentityProvider.class)) - .filter(providerFactory -> Objects.equals(providerFactory.getId(), providerId)) - .map(IdentityProviderFactory.class::cast) - .findFirst(); - - if (factory.isPresent()) { - return factory.get().createConfig(); - } else { - LOG.warn("Couldn't find a suitable identity provider factory for " + providerId); - return new IdentityProviderModel(); - } - } - - @Override - public void addIdentityProvider(IdentityProviderModel model) { - if (getIdentityProviderByAlias(model.getAlias()) != null) { - throw new ModelDuplicateException("An Identity Provider with given alias already exists."); - } - entity.addIdentityProvider(MapIdentityProviderEntity.fromModel(model)); - } - - @Override - public void removeIdentityProviderByAlias(String alias) { - IdentityProviderModel model = getIdentityProviderByAlias(alias); - entity.removeIdentityProvider(model.getInternalId()); - - // TODO: Sending an event should be extracted to store layer - session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderRemovedEvent() { - - @Override - public RealmModel getRealm() { - return MapRealmAdapter.this; - } - - @Override - public IdentityProviderModel getRemovedIdentityProvider() { - return model; - } - - @Override - public KeycloakSession getKeycloakSession() { - return session; - } - }); - // TODO: ^^^^^^^ Up to here - } - - @Override - public void updateIdentityProvider(IdentityProviderModel identityProvider) { - Set ips = entity.getIdentityProviders(); - if (ips != null) { - ips.stream() - .filter(ip -> Objects.equals(ip.getId(), identityProvider.getInternalId())) - .findFirst() - .ifPresent(oldPS -> { - oldPS.setAlias(identityProvider.getAlias()); - oldPS.setDisplayName(identityProvider.getDisplayName()); - oldPS.setProviderId(identityProvider.getProviderId()); - oldPS.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId()); - oldPS.setPostBrokerLoginFlowId(identityProvider.getPostBrokerLoginFlowId()); - oldPS.setEnabled(identityProvider.isEnabled()); - oldPS.setTrustEmail(identityProvider.isTrustEmail()); - oldPS.setStoreToken(identityProvider.isStoreToken()); - oldPS.setLinkOnly(identityProvider.isLinkOnly()); - oldPS.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate()); - oldPS.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault()); - oldPS.setConfig(identityProvider.getConfig() == null ? null : new HashMap<>(identityProvider.getConfig())); - }); - - // TODO: Sending an event should be extracted to store layer - session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderUpdatedEvent() { - - @Override - public RealmModel getRealm() { - return MapRealmAdapter.this; - } - - @Override - public IdentityProviderModel getUpdatedIdentityProvider() { - return identityProvider; - } - - @Override - public KeycloakSession getKeycloakSession() { - return session; - } - }); - // TODO: ^^^^^^^ Up to here - } - } - - @Override - public Stream getIdentityProviderMappersStream() { - Set ipms = entity.getIdentityProviderMappers(); - return ipms == null ? Stream.empty() : ipms.stream().map(MapIdentityProviderMapperEntity::toModel); - } - - @Override - public Stream getIdentityProviderMappersByAliasStream(String brokerAlias) { - Set ipms = entity.getIdentityProviderMappers(); - return ipms == null ? Stream.empty() : ipms.stream() - .filter(mapper -> Objects.equals(mapper.getIdentityProviderAlias(), brokerAlias)) - .map(MapIdentityProviderMapperEntity::toModel); - } - - @Override - public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) { - MapIdentityProviderMapperEntity identityProviderMapper = MapIdentityProviderMapperEntity.fromModel(model); - - if (entity.getIdentityProviderMapper(model.getId()).isPresent()) { - throw new ModelDuplicateException("An IdentityProviderMapper with given id already exists"); - } - - entity.addIdentityProviderMapper(identityProviderMapper); - - return MapIdentityProviderMapperEntity.toModel(identityProviderMapper); - } - - @Override - public void removeIdentityProviderMapper(IdentityProviderMapperModel model) { - entity.removeIdentityProviderMapper(model.getId()); - } - - @Override - public void updateIdentityProviderMapper(IdentityProviderMapperModel model) { - entity.getIdentityProviderMapper(model.getId()) - .ifPresent(oldIPM -> { - oldIPM.setName(model.getName()); - oldIPM.setIdentityProviderAlias(model.getIdentityProviderAlias()); - oldIPM.setIdentityProviderMapper(model.getIdentityProviderMapper()); - oldIPM.setConfig(model.getConfig()); - }); - } - - @Override - public IdentityProviderMapperModel getIdentityProviderMapperById(String id) { - if (id == null) return null; - return entity.getIdentityProviderMapper(id).map(MapIdentityProviderMapperEntity::toModel).orElse(null); - } - - @Override - public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name) { - Set ipms = entity.getIdentityProviderMappers(); - return ipms == null ? null : ipms.stream() - .filter(identityProviderMapper -> Objects.equals(identityProviderMapper.getIdentityProviderAlias(), brokerAlias) - && Objects.equals(identityProviderMapper.getName(), name)) - .findFirst() - .map(MapIdentityProviderMapperEntity::toModel) - .orElse(null); - } - - @Override - public ComponentModel addComponentModel(ComponentModel model) { - model = importComponentModel(model); - ComponentUtil.notifyCreated(session, this, model); - return model; - } - - /** - * Copied from jpa RealmAdapter: This just exists for testing purposes - */ - private static final String COMPONENT_PROVIDER_EXISTS_DISABLED = "component.provider.exists.disabled"; - - @Override - public ComponentModel importComponentModel(ComponentModel model) { - try { - ComponentFactory componentFactory = ComponentUtil.getComponentFactory(session, model); - if (componentFactory == null && System.getProperty(COMPONENT_PROVIDER_EXISTS_DISABLED) == null) { - throw new IllegalArgumentException("Invalid component type"); - } - componentFactory.validateConfiguration(session, this, model); - } catch (IllegalArgumentException | ComponentValidationException e) { - if (System.getProperty(COMPONENT_PROVIDER_EXISTS_DISABLED) == null) { - throw e; - } - } - - if (entity.getComponent(model.getId()).isPresent()) { - throw new ModelDuplicateException("A Component with given id already exists"); - } - - MapComponentEntity component = MapComponentEntity.fromModel(model); - if (model.getParentId() == null) { - component.setParentId(getId()); - } - entity.addComponent(component); - - return MapComponentEntity.toModel(component); - } - - @Override - public void updateComponent(ComponentModel component) { - ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, this, component); - entity.getComponent(component.getId()) - .ifPresent(existing -> { - ComponentModel oldModel = MapComponentEntity.toModel(existing); - updateComponent(existing, component); - ComponentUtil.notifyUpdated(session, this, oldModel, component); - }); - } - - private static void updateComponent(MapComponentEntity oldValue, ComponentModel newValue) { - oldValue.setName(newValue.getName()); - oldValue.setProviderId(newValue.getProviderId()); - oldValue.setProviderType(newValue.getProviderType()); - oldValue.setSubType(newValue.getSubType()); - oldValue.setParentId(newValue.getParentId()); - oldValue.setConfig(newValue.getConfig()); - } - - @Override - public void removeComponent(ComponentModel component) { - if (!entity.getComponent(component.getId()).isPresent()) return; - - session.users().preRemove(this, component); - ComponentUtil.notifyPreRemove(session, this, component); - removeComponents(component.getId()); - entity.removeComponent(component.getId()); - } - - @Override - public void removeComponents(String parentId) { - Set components = entity.getComponents(); - if (components == null || components.isEmpty()) return; - components.stream() - .filter(c -> Objects.equals(parentId, c.getParentId())) - .map(MapComponentEntity::toModel) - .collect(Collectors.toSet()) // This is necessary to read out all the components before removing them - .forEach(c -> { - session.users().preRemove(this, c); - ComponentUtil.notifyPreRemove(session, this, c); - entity.removeComponent(c.getId()); - }); - } - - @Override - public Stream getComponentsStream() { - Set components = entity.getComponents(); - return components == null ? Stream.empty() : components.stream().map(MapComponentEntity::toModel); - } - - @Override - public Stream getComponentsStream(String parentId) { - Set components = entity.getComponents(); - return components == null ? Stream.empty() : components.stream() - .filter(c -> Objects.equals(parentId, c.getParentId())) - .map(MapComponentEntity::toModel); - } - - @Override - public Stream getComponentsStream(String parentId, String providerType) { - Set components = entity.getComponents(); - return components == null ? Stream.empty() : components.stream() - .filter(c -> Objects.equals(parentId, c.getParentId())) - .filter(c -> Objects.equals(providerType, c.getProviderType())) - .map(MapComponentEntity::toModel); - } - - @Override - public ComponentModel getComponent(String id) { - return entity.getComponent(id).map(MapComponentEntity::toModel).orElse(null); - } - - @Override - public String getLoginTheme() { - return entity.getLoginTheme(); - } - - @Override - public void setLoginTheme(String name) { - entity.setLoginTheme(name); - } - - @Override - public String getAccountTheme() { - return entity.getAccountTheme(); - } - - @Override - public void setAccountTheme(String name) { - entity.setAccountTheme(name); - } - - @Override - public String getAdminTheme() { - return entity.getAdminTheme(); - } - - @Override - public void setAdminTheme(String name) { - entity.setAdminTheme(name); - } - - @Override - public String getEmailTheme() { - return entity.getEmailTheme(); - } - - @Override - public void setEmailTheme(String name) { - entity.setEmailTheme(name); - } - - @Override - public int getNotBefore() { - Long notBefore = entity.getNotBefore(); - return notBefore == null ? 0 : TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(notBefore); - } - - @Override - public void setNotBefore(int notBefore) { - entity.setNotBefore(TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(notBefore)); - } - - @Override - public boolean isEventsEnabled() { - Boolean is = entity.isEventsEnabled(); - return is == null ? false : is; - } - - @Override - public void setEventsEnabled(boolean enabled) { - entity.setEventsEnabled(enabled); - } - - @Override - public long getEventsExpiration() { - Long i = entity.getEventsExpiration(); - return i == null ? 0 : i; - } - - @Override - public void setEventsExpiration(long expiration) { - entity.setEventsExpiration(expiration); - } - - @Override - public Stream getEventsListenersStream() { - Set eLs = entity.getEventsListeners(); - return eLs == null ? Stream.empty() : eLs.stream(); - } - - @Override - public void setEventsListeners(Set listeners) { - entity.setEventsListeners(listeners); - } - - @Override - public Stream getEnabledEventTypesStream() { - Set eETs = entity.getEnabledEventTypes(); - return eETs == null ? Stream.empty() : eETs.stream(); - } - - @Override - public void setEnabledEventTypes(Set enabledEventTypes) { - entity.setEnabledEventTypes(enabledEventTypes); - } - - @Override - public boolean isAdminEventsEnabled() { - Boolean is = entity.isAdminEventsEnabled(); - return is == null ? false : is; - } - - @Override - public void setAdminEventsEnabled(boolean enabled) { - entity.setAdminEventsEnabled(enabled); - } - - @Override - public boolean isAdminEventsDetailsEnabled() { - Boolean is = entity.isAdminEventsDetailsEnabled(); - return is == null ? false : is; - } - - @Override - public void setAdminEventsDetailsEnabled(boolean enabled) { - entity.setAdminEventsDetailsEnabled(enabled); - } - - @Override - public ClientModel getMasterAdminClient() { - String masterAdminClientId = entity.getMasterAdminClient(); - if (masterAdminClientId == null) { - return null; - } - RealmModel masterRealm = getName().equals(Config.getAdminRealm()) - ? this - : session.realms().getRealmByName(Config.getAdminRealm()); - return session.clients().getClientById(masterRealm, masterAdminClientId); - } - - @Override - public void setMasterAdminClient(ClientModel client) { - String id = client == null ? null : client.getId(); - entity.setMasterAdminClient(id); - } - - @Override - public RoleModel getDefaultRole() { - return session.roles().getRoleById(this, entity.getDefaultRoleId()); - } - - @Override - public void setDefaultRole(RoleModel role) { - entity.setDefaultRoleId(role.getId()); - } - - @Override - public boolean isIdentityFederationEnabled() { - Set ips = entity.getIdentityProviders(); - return ips != null && ips.stream().findAny().isPresent(); - } - - @Override - public boolean isInternationalizationEnabled() { - Boolean is = entity.isInternationalizationEnabled(); - return is == null ? false : is; - } - - @Override - public void setInternationalizationEnabled(boolean enabled) { - entity.setInternationalizationEnabled(enabled); - } - - @Override - public Stream getSupportedLocalesStream() { - Set sLs = entity.getSupportedLocales(); - return sLs == null ? Stream.empty() : sLs.stream(); - } - - @Override - public void setSupportedLocales(Set locales) { - entity.setSupportedLocales(locales); - } - - @Override - public String getDefaultLocale() { - return entity.getDefaultLocale(); - } - - @Override - public void setDefaultLocale(String locale) { - entity.setDefaultLocale(locale); - } - - @Override - public GroupModel createGroup(String id, String name, GroupModel toParent) { - return session.groups().createGroup(this, id, name, toParent); - } - - @Override - public GroupModel getGroupById(String id) { - return session.groups().getGroupById(this, id); - } - - @Override - public Stream getGroupsStream() { - return session.groups().getGroupsStream(this); - } - - @Override - public Long getGroupsCount(Boolean onlyTopGroups) { - return session.groups().getGroupsCount(this, onlyTopGroups); - } - - @Override - public Long getGroupsCountByNameContaining(String search) { - return session.groups().getGroupsCountByNameContaining(this, search); - } - - @Override - public Stream getTopLevelGroupsStream() { - return session.groups().getTopLevelGroupsStream(this); - } - - @Override - public Stream getTopLevelGroupsStream(Integer first, Integer max) { - return session.groups().getTopLevelGroupsStream(this, first, max); - } - - @Override - public boolean removeGroup(GroupModel group) { - return session.groups().removeGroup(this, group); - } - - @Override - public void moveGroup(GroupModel group, GroupModel toParent) { - session.groups().moveGroup(this, group, toParent); - } - - @Override - public Stream getClientScopesStream() { - return session.clientScopes().getClientScopesStream(this); - } - - @Override - public ClientScopeModel addClientScope(String name) { - return session.clientScopes().addClientScope(this, name); - } - - @Override - public ClientScopeModel addClientScope(String id, String name) { - return session.clientScopes().addClientScope(this, id, name); - } - - @Override - public boolean removeClientScope(String id) { - return session.clientScopes().removeClientScope(this, id); - } - - @Override - public ClientScopeModel getClientScopeById(String id) { - return session.clientScopes().getClientScopeById(this, id); - } - - @Override - public void addDefaultClientScope(ClientScopeModel clientScope, boolean defaultScope) { - if (defaultScope) { - entity.addDefaultClientScopeId(clientScope.getId()); - } else { - entity.addOptionalClientScopeId(clientScope.getId()); - } - } - - @Override - public void removeDefaultClientScope(ClientScopeModel clientScope) { - Boolean removedDefault = entity.removeDefaultClientScopeId(clientScope.getId()); - if (removedDefault == null || !removedDefault) { - entity.removeOptionalClientScopeId(clientScope.getId()); - } - } - - @Override - public Stream getDefaultClientScopesStream(boolean defaultScope) { - Set csIds = defaultScope ? entity.getDefaultClientScopeIds() : entity.getOptionalClientScopeIds(); - return csIds == null ? Stream.empty() : csIds.stream().map(this::getClientScopeById); - } - - @Override - public void createOrUpdateRealmLocalizationTexts(String locale, Map localizationTexts) { - Map> realmLocalizationTexts = entity.getLocalizationTexts(); - - if (realmLocalizationTexts != null && realmLocalizationTexts.containsKey(locale)) { - Map currentTexts = new HashMap<>(realmLocalizationTexts.get(locale)); - currentTexts.putAll(localizationTexts); - entity.setLocalizationText(locale, currentTexts); - } else { - entity.setLocalizationText(locale, localizationTexts); - } - } - - @Override - public boolean removeRealmLocalizationTexts(String locale) { - if (locale == null) return false; - return entity.removeLocalizationText(locale); - } - - @Override - public Map> getRealmLocalizationTexts() { - Map> localizationTexts = entity.getLocalizationTexts(); - return localizationTexts == null ? Collections.emptyMap() : localizationTexts; - } - - @Override - public Map getRealmLocalizationTextsByLocale(String locale) { - Map lT = entity.getLocalizationText(locale); - return lT == null ? Collections.emptyMap() : lT; - } - - @Override - public RoleModel getRole(String name) { - return session.roles().getRealmRole(this, name); - } - - @Override - public RoleModel addRole(String name) { - return session.roles().addRealmRole(this, name); - } - - @Override - public RoleModel addRole(String id, String name) { - return session.roles().addRealmRole(this, id, name); - } - - @Override - public boolean removeRole(RoleModel role) { - return session.roles().removeRole(role); - } - - @Override - public Stream getRolesStream() { - return session.roles().getRealmRolesStream(this); - } - - @Override - public Stream getRolesStream(Integer firstResult, Integer maxResults) { - return session.roles().getRealmRolesStream(this, firstResult, maxResults); - } - - @Override - public Stream searchForRolesStream(String search, Integer first, Integer max) { - return session.roles().searchForRolesStream(this, search, first, max); - } - - @Override - public boolean isBruteForceProtected() { - return getAttribute(BRUTE_FORCE_PROTECTED, false); - } - - @Override - public void setBruteForceProtected(boolean value) { - setAttribute(BRUTE_FORCE_PROTECTED, value); - } - - @Override - public boolean isPermanentLockout() { - return getAttribute(PERMANENT_LOCKOUT, false); - } - - @Override - public void setPermanentLockout(final boolean val) { - setAttribute(PERMANENT_LOCKOUT, val); - } - - @Override - public int getMaxFailureWaitSeconds() { - return getAttribute(MAX_FAILURE_WAIT_SECONDS, 0); - } - - @Override - public void setMaxFailureWaitSeconds(int val) { - setAttribute(MAX_FAILURE_WAIT_SECONDS, val); - } - - @Override - public int getWaitIncrementSeconds() { - return getAttribute(WAIT_INCREMENT_SECONDS, 0); - } - - @Override - public void setWaitIncrementSeconds(int val) { - setAttribute(WAIT_INCREMENT_SECONDS, val); - } - - @Override - public int getMinimumQuickLoginWaitSeconds() { - return getAttribute(MINIMUM_QUICK_LOGIN_WAIT_SECONDS, 0); - } - - @Override - public void setMinimumQuickLoginWaitSeconds(int val) { - setAttribute(MINIMUM_QUICK_LOGIN_WAIT_SECONDS, val); - } - - @Override - public long getQuickLoginCheckMilliSeconds() { - return getAttribute(QUICK_LOGIN_CHECK_MILLISECONDS, 0L); - } - - @Override - public void setQuickLoginCheckMilliSeconds(long val) { - setAttribute(QUICK_LOGIN_CHECK_MILLISECONDS, val); - } - - @Override - public int getMaxDeltaTimeSeconds() { - return getAttribute(MAX_DELTA_SECONDS, 0); - } - - @Override - public void setMaxDeltaTimeSeconds(int val) { - setAttribute(MAX_DELTA_SECONDS, val); - } - - @Override - public int getFailureFactor() { - return getAttribute(FAILURE_FACTOR, 0); - } - - @Override - public void setFailureFactor(int failureFactor) { - setAttribute(FAILURE_FACTOR, failureFactor); - } - - @Override - public String getDefaultSignatureAlgorithm() { - return getAttribute(DEFAULT_SIGNATURE_ALGORITHM); - } - - @Override - public void setDefaultSignatureAlgorithm(String defaultSignatureAlgorithm) { - setAttribute(DEFAULT_SIGNATURE_ALGORITHM, defaultSignatureAlgorithm); - } - - @Override - public boolean isOfflineSessionMaxLifespanEnabled() { - Boolean is = entity.isOfflineSessionMaxLifespanEnabled(); - return is == null ? false : is; - } - - @Override - public void setOfflineSessionMaxLifespanEnabled(boolean offlineSessionMaxLifespanEnabled) { - entity.setOfflineSessionMaxLifespanEnabled(offlineSessionMaxLifespanEnabled); - } - - @Override - public int getOfflineSessionMaxLifespan() { - Integer i = entity.getOfflineSessionMaxLifespan(); - return i == null ? 0 : i; - } - - @Override - public void setOfflineSessionMaxLifespan(int seconds) { - entity.setOfflineSessionMaxLifespan(seconds); - } - - @Override - public WebAuthnPolicy getWebAuthnPolicy() { - MapWebAuthnPolicyEntity policy = entity.getWebAuthnPolicy(); - if (policy == null) policy = MapWebAuthnPolicyEntity.defaultWebAuthnPolicy(); - return MapWebAuthnPolicyEntity.toModel(policy); - } - - @Override - public void setWebAuthnPolicy(WebAuthnPolicy policy) { - entity.setWebAuthnPolicy(MapWebAuthnPolicyEntity.fromModel(policy)); - } - - @Override - public WebAuthnPolicy getWebAuthnPolicyPasswordless() { - MapWebAuthnPolicyEntity policy = entity.getWebAuthnPolicyPasswordless(); - if (policy == null) policy = MapWebAuthnPolicyEntity.defaultWebAuthnPolicy(); - return MapWebAuthnPolicyEntity.toModel(policy); - } - - @Override - public void setWebAuthnPolicyPasswordless(WebAuthnPolicy policy) { - entity.setWebAuthnPolicyPasswordless(MapWebAuthnPolicyEntity.fromModel(policy)); - } - - @Override - public Map getBrowserSecurityHeaders() { - Map bSH = entity.getBrowserSecurityHeaders(); - return bSH == null ? Collections.emptyMap() : Collections.unmodifiableMap(bSH); - } - - @Override - public void setBrowserSecurityHeaders(Map headers) { - entity.setBrowserSecurityHeaders(headers); - } - - @Override - public ClientInitialAccessModel createClientInitialAccessModel(int expiration, int count) { - MapClientInitialAccessEntity clientInitialAccess = MapClientInitialAccessEntity.createEntity(expiration, count); - entity.addClientInitialAccess(clientInitialAccess); - return MapClientInitialAccessEntity.toModel(clientInitialAccess); - } - - @Override - public ClientInitialAccessModel getClientInitialAccessModel(String id) { - return entity.getClientInitialAccess(id).map(MapClientInitialAccessEntity::toModel).orElse(null); - } - - @Override - public void removeClientInitialAccessModel(String id) { - entity.removeClientInitialAccess(id); - } - - @Override - public Stream getClientInitialAccesses() { - Set cias = entity.getClientInitialAccesses(); - return cias == null ? Stream.empty() : cias.stream().map(MapClientInitialAccessEntity::toModel); - } - - @Override - public void decreaseRemainingCount(ClientInitialAccessModel model) { - entity.getClientInitialAccess(model.getId()) - .ifPresent(cia -> cia.setRemainingCount(model.getRemainingCount() - 1)); - } - - @Override - public OAuth2DeviceConfig getOAuth2DeviceConfig() { - return new OAuth2DeviceConfig(this); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), hashCode()); - } - - @Override - public CibaConfig getCibaPolicy() { - return new CibaConfig(this); - } - - @Override - public ParConfig getParPolicy() { - return new ParConfig(this); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java deleted file mode 100644 index 4487a081be4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright 2022 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.realm; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; -import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity; -import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity; -import org.keycloak.models.map.realm.entity.MapComponentEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity; -import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity; -import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity; -import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity; -import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; -import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.realm.MapRealmEntity.AbstractRealmEntity" -) -@DeepCloner.Root -public interface MapRealmEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes { - - public abstract class AbstractRealmEntity extends UpdatableEntity.Impl implements MapRealmEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - @Override - public boolean isUpdated() { - return this.updated - || Optional.ofNullable(getAuthenticationExecutions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationExecutionEntity::isUpdated) - || Optional.ofNullable(getAuthenticationFlows()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationFlowEntity::isUpdated) - || Optional.ofNullable(getAuthenticatorConfigs()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticatorConfigEntity::isUpdated) - || Optional.ofNullable(getClientInitialAccesses()).orElseGet(Collections::emptySet).stream().anyMatch(MapClientInitialAccessEntity::isUpdated) - || Optional.ofNullable(getComponents()).orElseGet(Collections::emptySet).stream().anyMatch(MapComponentEntity::isUpdated) - || Optional.ofNullable(getIdentityProviders()).orElseGet(Collections::emptySet).stream().anyMatch(MapIdentityProviderEntity::isUpdated) - || Optional.ofNullable(getIdentityProviderMappers()).orElseGet(Collections::emptySet).stream().anyMatch(MapIdentityProviderMapperEntity::isUpdated) - || Optional.ofNullable(getRequiredActionProviders()).orElseGet(Collections::emptySet).stream().anyMatch(MapRequiredActionProviderEntity::isUpdated) - || Optional.ofNullable(getRequiredCredentials()).orElseGet(Collections::emptySet).stream().anyMatch(MapRequiredCredentialEntity::isUpdated) - || Optional.ofNullable(getOTPPolicy()).map(MapOTPPolicyEntity::isUpdated).orElse(false) - || Optional.ofNullable(getWebAuthnPolicy()).map(MapWebAuthnPolicyEntity::isUpdated).orElse(false) - || Optional.ofNullable(getWebAuthnPolicyPasswordless()).map(MapWebAuthnPolicyEntity::isUpdated).orElse(false); - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - Optional.ofNullable(getAuthenticationExecutions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getAuthenticationFlows()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getAuthenticatorConfigs()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getClientInitialAccesses()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getComponents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getIdentityProviders()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getIdentityProviderMappers()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getRequiredActionProviders()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getRequiredCredentials()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getOTPPolicy()).ifPresent(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getWebAuthnPolicy()).ifPresent(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getWebAuthnPolicyPasswordless()).ifPresent(UpdatableEntity::clearUpdatedFlag); - } - - @Override - public void removeExpiredClientInitialAccesses() { - Set cias = getClientInitialAccesses(); - if (cias != null) - cias.stream() - .filter(this::checkIfExpired) - .map(MapClientInitialAccessEntity::getId) - .collect(Collectors.toSet()) - .forEach(this::removeClientInitialAccess); - } - - @Override - public boolean hasClientInitialAccess() { - Set cias = getClientInitialAccesses(); - return cias != null && !cias.isEmpty(); - } - - private boolean checkIfExpired(MapClientInitialAccessEntity cia) { - return cia.getRemainingCount() < 1 || isExpired(cia, true); - } - } - - String getName(); - void setName(String name); - - String getDisplayName(); - void setDisplayName(String displayName); - - String getDisplayNameHtml(); - void setDisplayNameHtml(String displayNameHtml); - - Boolean isEnabled(); - void setEnabled(Boolean enabled); - - Boolean isRegistrationAllowed(); - void setRegistrationAllowed(Boolean registrationAllowed); - - Boolean isRegistrationEmailAsUsername(); - void setRegistrationEmailAsUsername(Boolean registrationEmailAsUsername); - - Boolean isVerifyEmail(); - void setVerifyEmail(Boolean verifyEmail); - - Boolean isResetPasswordAllowed(); - void setResetPasswordAllowed(Boolean resetPasswordAllowed); - - Boolean isLoginWithEmailAllowed(); - void setLoginWithEmailAllowed(Boolean loginWithEmailAllowed); - - Boolean isDuplicateEmailsAllowed(); - void setDuplicateEmailsAllowed(Boolean duplicateEmailsAllowed); - - Boolean isRememberMe(); - void setRememberMe(Boolean rememberMe); - - Boolean isEditUsernameAllowed(); - void setEditUsernameAllowed(Boolean editUsernameAllowed); - - Boolean isRevokeRefreshToken(); - void setRevokeRefreshToken(Boolean revokeRefreshToken); - - Boolean isAdminEventsEnabled(); - void setAdminEventsEnabled(Boolean adminEventsEnabled); - - Boolean isAdminEventsDetailsEnabled(); - void setAdminEventsDetailsEnabled(Boolean adminEventsDetailsEnabled); - - Boolean isInternationalizationEnabled(); - void setInternationalizationEnabled(Boolean internationalizationEnabled); - - Boolean isAllowUserManagedAccess(); - void setAllowUserManagedAccess(Boolean allowUserManagedAccess); - - Boolean isOfflineSessionMaxLifespanEnabled(); - void setOfflineSessionMaxLifespanEnabled(Boolean offlineSessionMaxLifespanEnabled); - - Boolean isEventsEnabled(); - void setEventsEnabled(Boolean eventsEnabled); - - Integer getRefreshTokenMaxReuse(); - void setRefreshTokenMaxReuse(Integer refreshTokenMaxReuse); - - Integer getSsoSessionIdleTimeout(); - void setSsoSessionIdleTimeout(Integer ssoSessionIdleTimeout); - - Integer getSsoSessionMaxLifespan(); - void setSsoSessionMaxLifespan(Integer ssoSessionMaxLifespan); - - Integer getSsoSessionIdleTimeoutRememberMe(); - void setSsoSessionIdleTimeoutRememberMe(Integer ssoSessionIdleTimeoutRememberMe); - - Integer getSsoSessionMaxLifespanRememberMe(); - void setSsoSessionMaxLifespanRememberMe(Integer ssoSessionMaxLifespanRememberMe); - - Integer getOfflineSessionIdleTimeout(); - void setOfflineSessionIdleTimeout(Integer offlineSessionIdleTimeout); - - Integer getAccessTokenLifespan(); - void setAccessTokenLifespan(Integer accessTokenLifespan); - - Integer getAccessTokenLifespanForImplicitFlow(); - void setAccessTokenLifespanForImplicitFlow(Integer accessTokenLifespanForImplicitFlow); - - Integer getAccessCodeLifespan(); - void setAccessCodeLifespan(Integer accessCodeLifespan); - - Integer getAccessCodeLifespanUserAction(); - void setAccessCodeLifespanUserAction(Integer accessCodeLifespanUserAction); - - Integer getAccessCodeLifespanLogin(); - void setAccessCodeLifespanLogin(Integer accessCodeLifespanLogin); - - Long getNotBefore(); - void setNotBefore(Long notBefore); - - Integer getClientSessionIdleTimeout(); - void setClientSessionIdleTimeout(Integer clientSessionIdleTimeout); - - Integer getClientSessionMaxLifespan(); - void setClientSessionMaxLifespan(Integer clientSessionMaxLifespan); - - Integer getClientOfflineSessionIdleTimeout(); - void setClientOfflineSessionIdleTimeout(Integer clientOfflineSessionIdleTimeout); - - Integer getClientOfflineSessionMaxLifespan(); - void setClientOfflineSessionMaxLifespan(Integer clientOfflineSessionMaxLifespan); - - Integer getActionTokenGeneratedByAdminLifespan(); - void setActionTokenGeneratedByAdminLifespan(Integer actionTokenGeneratedByAdminLifespan); - - Integer getOfflineSessionMaxLifespan(); - void setOfflineSessionMaxLifespan(Integer offlineSessionMaxLifespan); - - Long getEventsExpiration(); - void setEventsExpiration(Long eventsExpiration); - - String getPasswordPolicy(); - void setPasswordPolicy(String passwordPolicy); - - String getSslRequired(); - void setSslRequired(String sslRequired); - - String getLoginTheme(); - void setLoginTheme(String loginTheme); - - String getAccountTheme(); - void setAccountTheme(String accountTheme); - - String getAdminTheme(); - void setAdminTheme(String adminTheme); - - String getEmailTheme(); - void setEmailTheme(String emailTheme); - - String getMasterAdminClient(); - void setMasterAdminClient(String masterAdminClient); - - String getDefaultRoleId(); - void setDefaultRoleId(String defaultRoleId); - - String getDefaultLocale(); - void setDefaultLocale(String defaultLocale); - - String getBrowserFlow(); - void setBrowserFlow(String browserFlow); - - String getRegistrationFlow(); - void setRegistrationFlow(String registrationFlow); - - String getDirectGrantFlow(); - void setDirectGrantFlow(String directGrantFlow); - - String getResetCredentialsFlow(); - void setResetCredentialsFlow(String resetCredentialsFlow); - - String getClientAuthenticationFlow(); - void setClientAuthenticationFlow(String clientAuthenticationFlow); - - String getDockerAuthenticationFlow(); - void setDockerAuthenticationFlow(String dockerAuthenticationFlow); - - MapOTPPolicyEntity getOTPPolicy(); - void setOTPPolicy(MapOTPPolicyEntity otpPolicy); - - MapWebAuthnPolicyEntity getWebAuthnPolicy(); - void setWebAuthnPolicy(MapWebAuthnPolicyEntity webAuthnPolicy); - - MapWebAuthnPolicyEntity getWebAuthnPolicyPasswordless(); - void setWebAuthnPolicyPasswordless(MapWebAuthnPolicyEntity webAuthnPolicyPasswordless); - - Set getDefaultClientScopeIds(); - void addDefaultClientScopeId(String scopeId); - Boolean removeDefaultClientScopeId(String scopeId); - - Set getOptionalClientScopeIds(); - void addOptionalClientScopeId(String scopeId); - Boolean removeOptionalClientScopeId(String scopeId); - - Set getDefaultGroupIds(); - void addDefaultGroupId(String groupId); - void removeDefaultGroupId(String groupId); - - Set getEventsListeners(); - void setEventsListeners(Set eventsListeners); - - Set getEnabledEventTypes(); - void setEnabledEventTypes(Set enabledEventTypes); - - Set getSupportedLocales(); - void setSupportedLocales(Set supportedLocales); - - Map> getLocalizationTexts(); - Map getLocalizationText(String locale); - void setLocalizationText(String locale, Map texts); - Boolean removeLocalizationText(String locale); - - Map getBrowserSecurityHeaders(); - void setBrowserSecurityHeaders(Map headers); - void setBrowserSecurityHeader(String name, String value); - - Map getSmtpConfig(); - void setSmtpConfig(Map smtpConfig); - - Set getRequiredCredentials(); - void addRequiredCredential(MapRequiredCredentialEntity requiredCredential); - - Set getComponents(); - Optional getComponent(String id); - void addComponent(MapComponentEntity component); - Boolean removeComponent(String componentId); - - Set getAuthenticationFlows(); - Optional getAuthenticationFlow(String flowId); - void addAuthenticationFlow(MapAuthenticationFlowEntity authenticationFlow); - Boolean removeAuthenticationFlow(String flowId); - - Set getAuthenticationExecutions(); - Optional getAuthenticationExecution(String id); - void addAuthenticationExecution(MapAuthenticationExecutionEntity authenticationExecution); - Boolean removeAuthenticationExecution(String executionId); - - Set getAuthenticatorConfigs(); - void addAuthenticatorConfig(MapAuthenticatorConfigEntity authenticatorConfig); - Optional getAuthenticatorConfig(String authenticatorConfigId); - Boolean removeAuthenticatorConfig(String authenticatorConfigId); - - Set getRequiredActionProviders(); - void addRequiredActionProvider(MapRequiredActionProviderEntity requiredActionProvider); - Optional getRequiredActionProvider(String requiredActionProviderId); - Boolean removeRequiredActionProvider(String requiredActionProviderId); - - Set getIdentityProviders(); - void addIdentityProvider(MapIdentityProviderEntity identityProvider); - Boolean removeIdentityProvider(String identityProviderId); - - Set getIdentityProviderMappers(); - void addIdentityProviderMapper(MapIdentityProviderMapperEntity identityProviderMapper); - Boolean removeIdentityProviderMapper(String identityProviderMapperId); - Optional getIdentityProviderMapper(String identityProviderMapperId); - - Set getClientInitialAccesses(); - void addClientInitialAccess(MapClientInitialAccessEntity clientInitialAccess); - Optional getClientInitialAccess(String clientInitialAccessId); - Boolean removeClientInitialAccess(String clientInitialAccessId); - @IgnoreForEntityImplementationGenerator - void removeExpiredClientInitialAccesses(); - @IgnoreForEntityImplementationGenerator - boolean hasClientInitialAccess(); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java deleted file mode 100644 index 390b30a90c6..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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.realm; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Stream; -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmModel.SearchableFields; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.KeycloakModelUtils; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapRealmProvider implements RealmProvider { - - private static final Logger LOG = Logger.getLogger(MapRealmProvider.class); - private final KeycloakSession session; - final MapStorage store; - - public MapRealmProvider(KeycloakSession session, MapStorage realmStore) { - this.session = session; - this.store = realmStore; - } - - private RealmModel entityToAdapter(MapRealmEntity entity) { - return new MapRealmAdapter(session, entity); - } - - @Override - public RealmModel createRealm(String name) { - return createRealm(KeycloakModelUtils.generateId(), name); - } - - @Override - public RealmModel createRealm(String id, String name) { - if (getRealmByName(name) != null) { - throw new ModelDuplicateException("Realm with given name exists: " + name); - } - - if (id != null && store.exists(id)) { - throw new ModelDuplicateException("Realm exists: " + id); - } - - LOG.tracef("createRealm(%s, %s)%s", id, name, getShortStackTrace()); - - MapRealmEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapRealmEntity.class); - entity.setId(id); - entity.setName(name); - - entity = store.create(entity); - return entityToAdapter(entity); - } - - @Override - public RealmModel getRealm(String id) { - if (id == null) return null; - - LOG.tracef("getRealm(%s)%s", id, getShortStackTrace()); - - MapRealmEntity entity = store.read(id); - return entity == null ? null : entityToAdapter(entity); - } - - @Override - public RealmModel getRealmByName(String name) { - if (name == null) return null; - - LOG.tracef("getRealmByName(%s)%s", name, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.NAME, Operator.EQ, name); - - String realmId = store.read(withCriteria(mcb)) - .findFirst() - .map(MapRealmEntity::getId) - .orElse(null); - //we need to go via session.realms() not to bypass cache - return realmId == null ? null : session.realms().getRealm(realmId); - } - - @Override - public Stream getRealmsStream() { - return getRealmsStream(criteria()); - } - - @Override - public Stream getRealmsWithProviderTypeStream(Class type) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.COMPONENT_PROVIDER_TYPE, Operator.EQ, type.getName()); - - return getRealmsStream(mcb); - } - - private Stream getRealmsStream(DefaultModelCriteria mcb) { - return store.read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) - .map(this::entityToAdapter); - } - - @Override - public boolean removeRealm(String id) { - LOG.tracef("removeRealm(%s)%s", id, getShortStackTrace()); - - RealmModel realm = getRealm(id); - - if (realm == null) return false; - - session.invalidate(REALM_BEFORE_REMOVE, realm); - - store.delete(id); - - session.invalidate(REALM_AFTER_REMOVE, realm); - - return true; - } - - @Override - public void removeExpiredClientInitialAccess() { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.CLIENT_INITIAL_ACCESS, Operator.EXISTS); - - store.read(withCriteria(mcb)) - .forEach(MapRealmEntity::removeExpiredClientInitialAccesses); - } - - //TODO move the following method to adapter - @Override - public void saveLocalizationText(RealmModel realm, String locale, String key, String text) { - if (locale == null || key == null || text == null) return; - Map texts = new HashMap<>(); - texts.put(key, text); - realm.createOrUpdateRealmLocalizationTexts(locale, texts); - } - - //TODO move the following method to adapter - @Override - public void saveLocalizationTexts(RealmModel realm, String locale, Map localizationTexts) { - if (locale == null || localizationTexts == null) return; - realm.createOrUpdateRealmLocalizationTexts(locale, localizationTexts); - } - - //TODO move the following method to adapter - @Override - public boolean updateLocalizationText(RealmModel realm, String locale, String key, String text) { - if (locale == null || key == null || text == null || (! realm.getRealmLocalizationTextsByLocale(locale).containsKey(key))) return false; - saveLocalizationText(realm, locale, key, text); - return true; - } - - //TODO move the following method to adapter - @Override - public boolean deleteLocalizationTextsByLocale(RealmModel realm, String locale) { - return realm.removeRealmLocalizationTexts(locale); - } - - //TODO move the following method to adapter - @Override - public boolean deleteLocalizationText(RealmModel realm, String locale, String key) { - if (locale == null || key == null || (! realm.getRealmLocalizationTextsByLocale(locale).containsKey(key))) return false; - - Map texts = new HashMap<>(realm.getRealmLocalizationTextsByLocale(locale)); - texts.remove(key); - realm.removeRealmLocalizationTexts(locale); - realm.createOrUpdateRealmLocalizationTexts(locale, texts); - return true; - } - - //TODO move the following method to adapter - @Override - public String getLocalizationTextsById(RealmModel realm, String locale, String key) { - if (locale == null || key == null || (! realm.getRealmLocalizationTextsByLocale(locale).containsKey(key))) return null; - return realm.getRealmLocalizationTextsByLocale(locale).get(key); - } - - @Override - public void close() { - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProviderFactory.java deleted file mode 100644 index 8868f1c3533..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProviderFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.realm; - -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_AFTER_REMOVE; - -public class MapRealmProviderFactory extends AbstractMapProviderFactory implements RealmProviderFactory, InvalidationHandler { - - public MapRealmProviderFactory() { - super(RealmModel.class, MapRealmProvider.class); - } - - @Override - public MapRealmProvider createNew(KeycloakSession session) { - return new MapRealmProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "Realm provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_AFTER_REMOVE) { - session.getKeycloakSessionFactory().publish(new RealmModel.RealmRemovedEvent() { - @Override public RealmModel getRealm() { return (RealmModel) params[0]; } - @Override public KeycloakSession getKeycloakSession() { return session; } - }); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticationExecutionEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticationExecutionEntity.java deleted file mode 100644 index 1aafc7eea60..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticationExecutionEntity.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapAuthenticationExecutionEntity extends UpdatableEntity, AbstractEntity { - static MapAuthenticationExecutionEntity fromModel(AuthenticationExecutionModel model) { - if (model == null) return null; - MapAuthenticationExecutionEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapAuthenticationExecutionEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - entity.setId(id); - entity.setAuthenticator(model.getAuthenticator()); - entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); - entity.setFlowId(model.getFlowId()); - entity.setParentFlowId(model.getParentFlow()); - entity.setRequirement(model.getRequirement()); - entity.setAutheticatorFlow(model.isAuthenticatorFlow()); - entity.setPriority(model.getPriority()); - return entity; - } - - static AuthenticationExecutionModel toModel(MapAuthenticationExecutionEntity entity) { - if (entity == null) return null; - AuthenticationExecutionModel model = new AuthenticationExecutionModel(); - model.setId(entity.getId()); - model.setAuthenticator(entity.getAuthenticator()); - model.setAuthenticatorConfig(entity.getAuthenticatorConfig()); - model.setFlowId(entity.getFlowId()); - model.setParentFlow(entity.getParentFlowId()); - model.setRequirement(entity.getRequirement()); - Boolean authenticatorFlow = entity.isAutheticatorFlow(); - model.setAuthenticatorFlow(authenticatorFlow == null ? false : authenticatorFlow); - Integer priority = entity.getPriority(); - model.setPriority(priority == null ? 0 : priority); - return model; - } - - String getAuthenticator(); - void setAuthenticator(String authenticator); - - String getAuthenticatorConfig(); - void setAuthenticatorConfig(String authenticatorConfig); - - AuthenticationExecutionModel.Requirement getRequirement(); - void setRequirement(AuthenticationExecutionModel.Requirement requirement); - - Boolean isAutheticatorFlow(); - void setAutheticatorFlow(Boolean autheticatorFlow); - - String getFlowId(); - void setFlowId(String flowId); - - String getParentFlowId(); - void setParentFlowId(String parentFlowId); - - Integer getPriority(); - void setPriority(Integer priority); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticationFlowEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticationFlowEntity.java deleted file mode 100644 index 4b106c2c678..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticationFlowEntity.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapAuthenticationFlowEntity extends UpdatableEntity, AbstractEntity { - static MapAuthenticationFlowEntity fromModel(AuthenticationFlowModel model) { - MapAuthenticationFlowEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapAuthenticationFlowEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - entity.setId(id); - entity.setAlias(model.getAlias()); - entity.setBuiltIn(model.isBuiltIn()); - entity.setDescription(model.getDescription()); - entity.setProviderId(model.getProviderId()); - entity.setTopLevel(model.isTopLevel()); - - return entity; - } - - static AuthenticationFlowModel toModel(MapAuthenticationFlowEntity entity) { - AuthenticationFlowModel model = new AuthenticationFlowModel(); - model.setId(entity.getId()); - model.setAlias(entity.getAlias()); - Boolean builtIn = entity.isBuiltIn(); - model.setBuiltIn(builtIn == null ? false : builtIn); - model.setDescription(entity.getDescription()); - model.setProviderId(entity.getProviderId()); - Boolean topLevel = entity.isTopLevel(); - model.setTopLevel(topLevel == null ? false : topLevel); - return model; - } - - String getAlias(); - void setAlias(String alias); - - String getDescription(); - void setDescription(String description); - - String getProviderId(); - void setProviderId(String providerId); - - Boolean isBuiltIn(); - void setBuiltIn(Boolean builtIn); - - Boolean isTopLevel(); - void setTopLevel(Boolean topLevel); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticatorConfigEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticatorConfigEntity.java deleted file mode 100644 index bd606cad93d..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapAuthenticatorConfigEntity.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.AuthenticatorConfigModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.HashMap; -import java.util.Map; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapAuthenticatorConfigEntity extends UpdatableEntity, AbstractEntity { - static MapAuthenticatorConfigEntity fromModel(AuthenticatorConfigModel model) { - if (model == null) return null; - MapAuthenticatorConfigEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapAuthenticatorConfigEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - entity.setId(id); - entity.setAlias(model.getAlias()); - entity.setConfig(model.getConfig()); - return entity; - } - - static AuthenticatorConfigModel toModel(MapAuthenticatorConfigEntity entity) { - if (entity == null) return null; - AuthenticatorConfigModel model = new AuthenticatorConfigModel(); - model.setId(entity.getId()); - model.setAlias(entity.getAlias()); - Map config = new HashMap<>(); - if (entity.getConfig() != null) config.putAll(entity.getConfig()); - model.setConfig(config); - return model; - } - - String getAlias(); - void setAlias(String alias); - - Map getConfig(); - void setConfig(Map config); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapClientInitialAccessEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapClientInitialAccessEntity.java deleted file mode 100644 index 4dc733f0a15..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapClientInitialAccessEntity.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.common.util.Time; -import org.keycloak.models.ClientInitialAccessModel; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapClientInitialAccessEntity extends UpdatableEntity, AbstractEntity, ExpirableEntity { - static MapClientInitialAccessEntity createEntity(int expiration, int count) { - long currentTime = Time.currentTimeMillis(); - - MapClientInitialAccessEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapClientInitialAccessEntity.class); - entity.setId(KeycloakModelUtils.generateId()); - entity.setTimestamp(currentTime); - entity.setExpiration(expiration == 0 ? null : currentTime + TimeAdapter.fromSecondsToMilliseconds(expiration)); - entity.setCount(count); - entity.setRemainingCount(count); - return entity; - } - - static ClientInitialAccessModel toModel(MapClientInitialAccessEntity entity) { - if (entity == null) return null; - ClientInitialAccessModel model = new ClientInitialAccessModel(); - model.setId(entity.getId()); - Long timestampSeconds = TimeAdapter.fromMilliSecondsToSeconds(entity.getTimestamp()); - model.setTimestamp(timestampSeconds == null ? 0 : TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(timestampSeconds)); - Long expirationSeconds = TimeAdapter.fromMilliSecondsToSeconds(entity.getExpiration()); - model.setExpiration(expirationSeconds == null ? 0 : TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(expirationSeconds - model.getTimestamp())); - Integer count = entity.getCount(); - model.setCount(count == null ? 0 : count); - Integer remainingCount = entity.getRemainingCount(); - model.setRemainingCount(remainingCount == null ? 0 : remainingCount); - return model; - } - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the client initial access entity was created. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the client initial access entity was created. - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - Integer getCount(); - void setCount(Integer count); - - Integer getRemainingCount(); - void setRemainingCount(Integer remainingCount); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapComponentEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapComponentEntity.java deleted file mode 100644 index 8feb5d98fd5..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapComponentEntity.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.List; -import java.util.Map; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapComponentEntity extends UpdatableEntity, AbstractEntity { - static MapComponentEntity fromModel(ComponentModel model) { - MapComponentEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapComponentEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - entity.setId(id); - entity.setName(model.getName()); - entity.setProviderId(model.getProviderId()); - entity.setProviderType(model.getProviderType()); - entity.setSubType(model.getSubType()); - entity.setParentId(model.getParentId()); - entity.setConfig(model.getConfig()); - return entity; - } - - static ComponentModel toModel(MapComponentEntity entity) { - ComponentModel model = new ComponentModel(); - model.setId(entity.getId()); - model.setName(entity.getName()); - model.setProviderId(entity.getProviderId()); - model.setProviderType(entity.getProviderType()); - model.setSubType(entity.getSubType()); - model.setParentId(entity.getParentId()); - Map> config = entity.getConfig(); - model.setConfig(config == null ? new MultivaluedHashMap<>() : new MultivaluedHashMap<>(config)); - return model; - } - - String getName(); - void setName(String name); - - String getProviderId(); - void setProviderId(String providerId); - - String getProviderType(); - void setProviderType(String providerType); - - String getSubType(); - void setSubType(String subType); - - String getParentId(); - void setParentId(String parentId); - - Map> getConfig(); - void setConfig(Map> config); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapIdentityProviderEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapIdentityProviderEntity.java deleted file mode 100644 index 254fd1e4851..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapIdentityProviderEntity.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapIdentityProviderEntity extends UpdatableEntity, AbstractEntity { - static MapIdentityProviderEntity fromModel(IdentityProviderModel model) { - if (model == null) return null; - MapIdentityProviderEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapIdentityProviderEntity.class); - String id = model.getInternalId() == null ? KeycloakModelUtils.generateId() : model.getInternalId(); - entity.setId(id); - entity.setAlias(model.getAlias()); - entity.setDisplayName(model.getDisplayName()); - entity.setProviderId(model.getProviderId()); - entity.setFirstBrokerLoginFlowId(model.getFirstBrokerLoginFlowId()); - entity.setPostBrokerLoginFlowId(model.getPostBrokerLoginFlowId()); - entity.setEnabled(model.isEnabled()); - entity.setTrustEmail(model.isTrustEmail()); - entity.setStoreToken(model.isStoreToken()); - entity.setLinkOnly(model.isLinkOnly()); - entity.setAddReadTokenRoleOnCreate(model.isAddReadTokenRoleOnCreate()); - entity.setAuthenticateByDefault(model.isAuthenticateByDefault()); - entity.setConfig(model.getConfig()); - return entity; - } - - static IdentityProviderModel toModel(MapIdentityProviderEntity entity, Supplier instanceCreator) { - if (entity == null) return null; - IdentityProviderModel model = instanceCreator.get(); - model.setInternalId(entity.getId()); - model.setAlias(entity.getAlias()); - model.setDisplayName(entity.getDisplayName()); - model.setProviderId(entity.getProviderId()); - model.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId()); - model.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId()); - Boolean enabled = entity.isEnabled(); - model.setEnabled(enabled == null ? false : enabled); - Boolean trustEmail = entity.isTrustEmail(); - model.setTrustEmail(trustEmail == null ? false : trustEmail); - Boolean storeToken = entity.isStoreToken(); - model.setStoreToken(storeToken == null ? false : storeToken); - Boolean linkOnly = entity.isLinkOnly(); - model.setLinkOnly(linkOnly == null ? false : linkOnly); - Boolean addReadTokenRoleOnCreate = entity.isAddReadTokenRoleOnCreate(); - model.setAddReadTokenRoleOnCreate(addReadTokenRoleOnCreate == null ? false : addReadTokenRoleOnCreate); - Boolean authenticateByDefault = entity.isAuthenticateByDefault(); - model.setAuthenticateByDefault(authenticateByDefault == null ? false : authenticateByDefault); - Map config = entity.getConfig(); - model.setConfig(config == null ? new HashMap<>() : new HashMap<>(config)); - return model; - } - - String getAlias(); - void setAlias(String alias); - - String getDisplayName(); - void setDisplayName(String displayName); - - String getProviderId(); - void setProviderId(String providerId); - - String getFirstBrokerLoginFlowId(); - void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId); - - String getPostBrokerLoginFlowId(); - void setPostBrokerLoginFlowId(String postBrokerLoginFlowId); - - Boolean isEnabled(); - void setEnabled(Boolean enabled); - - Boolean isTrustEmail(); - void setTrustEmail(Boolean trustEmail); - - Boolean isStoreToken(); - void setStoreToken(Boolean storeToken); - - Boolean isLinkOnly(); - void setLinkOnly(Boolean linkOnly); - - Boolean isAddReadTokenRoleOnCreate(); - void setAddReadTokenRoleOnCreate(Boolean addReadTokenRoleOnCreate); - - Boolean isAuthenticateByDefault(); - void setAuthenticateByDefault(Boolean authenticateByDefault); - - Map getConfig(); - void setConfig(Map config); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapIdentityProviderMapperEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapIdentityProviderMapperEntity.java deleted file mode 100644 index ece2b94f528..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapIdentityProviderMapperEntity.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.IdentityProviderMapperModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.HashMap; -import java.util.Map; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapIdentityProviderMapperEntity extends UpdatableEntity, AbstractEntity { - static MapIdentityProviderMapperEntity fromModel(IdentityProviderMapperModel model) { - if (model == null) return null; - MapIdentityProviderMapperEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapIdentityProviderMapperEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - entity.setId(id); - entity.setName(model.getName()); - entity.setIdentityProviderAlias(model.getIdentityProviderAlias()); - entity.setIdentityProviderMapper(model.getIdentityProviderMapper()); - entity.setConfig(model.getConfig()); - return entity; - } - - static IdentityProviderMapperModel toModel(MapIdentityProviderMapperEntity entity) { - if (entity == null) return null; - IdentityProviderMapperModel model = new IdentityProviderMapperModel(); - model.setId(entity.getId()); - model.setName(entity.getName()); - model.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - model.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = entity.getConfig(); - model.setConfig(config == null ? new HashMap<>() : new HashMap<>(config)); - return model; - } - - String getName(); - void setName(String name); - - String getIdentityProviderAlias(); - void setIdentityProviderAlias(String identityProviderAlias); - - String getIdentityProviderMapper(); - void setIdentityProviderMapper(String identityProviderMapper); - - Map getConfig(); - void setConfig(Map config); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapOTPPolicyEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapOTPPolicyEntity.java deleted file mode 100644 index 429a9a69000..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapOTPPolicyEntity.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapOTPPolicyEntity extends UpdatableEntity { - static MapOTPPolicyEntity fromModel(OTPPolicy model) { - if (model == null) return null; - MapOTPPolicyEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapOTPPolicyEntity.class); - entity.setOtpPolicyAlgorithm(model.getAlgorithm()); - entity.setOtpPolicyDigits(model.getDigits()); - entity.setOtpPolicyInitialCounter(model.getInitialCounter()); - entity.setOtpPolicyLookAheadWindow(model.getLookAheadWindow()); - entity.setOtpPolicyType(model.getType()); - entity.setOtpPolicyPeriod(model.getPeriod()); - entity.setOtpPolicyCodeReusable(model.isCodeReusable()); - return entity; - } - - static OTPPolicy toModel(MapOTPPolicyEntity entity) { - if (entity == null) return null; - OTPPolicy model = new OTPPolicy(); - - Integer otpPolicyDigits = entity.getOtpPolicyDigits(); - model.setDigits(otpPolicyDigits == null ? 0 : otpPolicyDigits); - model.setAlgorithm(entity.getOtpPolicyAlgorithm()); - - Integer otpPolicyInitialCounter = entity.getOtpPolicyInitialCounter(); - model.setInitialCounter(otpPolicyInitialCounter == null ? 0 : otpPolicyInitialCounter); - - Integer otpPolicyLookAheadWindow = entity.getOtpPolicyLookAheadWindow(); - model.setLookAheadWindow(otpPolicyLookAheadWindow == null ? 0 : otpPolicyLookAheadWindow); - model.setType(entity.getOtpPolicyType()); - - Integer otpPolicyPeriod = entity.getOtpPolicyPeriod(); - model.setPeriod(otpPolicyPeriod == null ? 0 : otpPolicyPeriod); - - Boolean isOtpPolicyReusable = entity.isOtpPolicyCodeReusable(); - model.setCodeReusable(isOtpPolicyReusable == null ? OTPPolicy.DEFAULT_IS_REUSABLE : isOtpPolicyReusable); - - return model; - } - - Integer getOtpPolicyInitialCounter(); - void setOtpPolicyInitialCounter(Integer otpPolicyInitialCounter); - - Integer getOtpPolicyDigits(); - void setOtpPolicyDigits(Integer otpPolicyDigits); - - Integer getOtpPolicyLookAheadWindow(); - void setOtpPolicyLookAheadWindow(Integer otpPolicyLookAheadWindow); - - Integer getOtpPolicyPeriod(); - void setOtpPolicyPeriod(Integer otpPolicyPeriod); - - String getOtpPolicyType(); - void setOtpPolicyType(String otpPolicyType); - - String getOtpPolicyAlgorithm(); - void setOtpPolicyAlgorithm(String otpPolicyAlgorithm); - - Boolean isOtpPolicyCodeReusable(); - void setOtpPolicyCodeReusable(Boolean isOtpPolicyCodeReusable); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapRequiredActionProviderEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapRequiredActionProviderEntity.java deleted file mode 100644 index c9728a0c708..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapRequiredActionProviderEntity.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.HashMap; -import java.util.Map; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapRequiredActionProviderEntity extends UpdatableEntity, AbstractEntity { - static MapRequiredActionProviderEntity fromModel(RequiredActionProviderModel model) { - if (model == null) return null; - MapRequiredActionProviderEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapRequiredActionProviderEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - entity.setId(id); - entity.setAlias(model.getAlias()); - entity.setName(model.getName()); - entity.setProviderId(model.getProviderId()); - entity.setPriority(model.getPriority()); - entity.setEnabled(model.isEnabled()); - entity.setDefaultAction(model.isDefaultAction()); - entity.setConfig(model.getConfig()); - return entity; - } - - static RequiredActionProviderModel toModel(MapRequiredActionProviderEntity entity) { - if (entity == null) return null; - RequiredActionProviderModel model = new RequiredActionProviderModel(); - model.setId(entity.getId()); - model.setAlias(entity.getAlias()); - model.setName(entity.getName()); - model.setProviderId(entity.getProviderId()); - Integer priority = entity.getPriority(); - model.setPriority(priority == null ? 0 : priority); - Boolean enabled = entity.isEnabled(); - model.setEnabled(enabled == null ? false : enabled); - Boolean defaultAction = entity.isDefaultAction(); - model.setDefaultAction(defaultAction == null ? false : defaultAction); - Map config = entity.getConfig(); - model.setConfig(config == null ? new HashMap<>() : new HashMap<>(config)); - return model; - } - - String getAlias(); - void setAlias(String alias); - - String getName(); - void setName(String name); - - String getProviderId(); - void setProviderId(String providerId); - - Integer getPriority(); - void setPriority(Integer priority); - - Boolean isEnabled(); - void setEnabled(Boolean enabled); - - Boolean isDefaultAction(); - void setDefaultAction(Boolean defaultAction); - - Map getConfig(); - void setConfig(Map config); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapRequiredCredentialEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapRequiredCredentialEntity.java deleted file mode 100644 index cfe98fbada5..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapRequiredCredentialEntity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapRequiredCredentialEntity extends UpdatableEntity { - static MapRequiredCredentialEntity fromModel(RequiredCredentialModel model) { - if (model == null) return null; - MapRequiredCredentialEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapRequiredCredentialEntity.class); - entity.setFormLabel(model.getFormLabel()); - entity.setType(model.getType()); - entity.setInput(model.isInput()); - entity.setSecret(model.isSecret()); - return entity; - } - - static RequiredCredentialModel toModel(MapRequiredCredentialEntity entity) { - if (entity == null) return null; - RequiredCredentialModel model = new RequiredCredentialModel(); - model.setFormLabel(entity.getFormLabel()); - model.setType(entity.getType()); - Boolean secret = entity.isSecret(); - model.setSecret(secret == null ? false : secret); - Boolean input = entity.isInput(); - model.setInput(input == null ? false : input); - return model; - } - - String getType(); - void setType(String type); - - String getFormLabel(); - void setFormLabel(String formLabel); - - Boolean isSecret(); - void setSecret(Boolean secret); - - Boolean isInput(); - void setInput(Boolean input); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapWebAuthnPolicyEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapWebAuthnPolicyEntity.java deleted file mode 100644 index 5087ff20702..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/realm/entity/MapWebAuthnPolicyEntity.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2022 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.realm.entity; - -import org.keycloak.models.Constants; -import org.keycloak.models.WebAuthnPolicy; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapWebAuthnPolicyEntity extends UpdatableEntity { - static MapWebAuthnPolicyEntity fromModel(WebAuthnPolicy model) { - if (model == null) return null; - MapWebAuthnPolicyEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapWebAuthnPolicyEntity.class); - entity.setRpEntityName(model.getRpEntityName()); - entity.setSignatureAlgorithms(model.getSignatureAlgorithm()); - entity.setRpId(model.getRpId()); - entity.setAttestationConveyancePreference(model.getAttestationConveyancePreference()); - entity.setAuthenticatorAttachment(model.getAuthenticatorAttachment()); - entity.setRequireResidentKey(model.getRequireResidentKey()); - entity.setUserVerificationRequirement(model.getUserVerificationRequirement()); - entity.setCreateTimeout(model.getCreateTimeout()); - entity.setAvoidSameAuthenticatorRegister(model.isAvoidSameAuthenticatorRegister()); - entity.setAcceptableAaguids(model.getAcceptableAaguids()); - entity.setExtraOrigins(model.getExtraOrigins()); - return entity; - } - - static WebAuthnPolicy toModel(MapWebAuthnPolicyEntity entity) { - if (entity == null) return null; - WebAuthnPolicy model = new WebAuthnPolicy(); - model.setRpEntityName(entity.getRpEntityName()); - model.setSignatureAlgorithm(entity.getSignatureAlgorithms()); - model.setRpId(entity.getRpId()); - model.setAttestationConveyancePreference(entity.getAttestationConveyancePreference()); - model.setAuthenticatorAttachment(entity.getAuthenticatorAttachment()); - model.setRequireResidentKey(entity.getRequireResidentKey()); - model.setUserVerificationRequirement(entity.getUserVerificationRequirement()); - model.setCreateTimeout(entity.getCreateTimeout()); - model.setAvoidSameAuthenticatorRegister(entity.isAvoidSameAuthenticatorRegister()); - List acceptableAaguids = entity.getAcceptableAaguids(); - model.setAcceptableAaguids(acceptableAaguids == null ? new LinkedList<>() : new LinkedList<>(acceptableAaguids)); - List extraOrigins = entity.getExtraOrigins(); - model.setExtraOrigins(extraOrigins == null ? new LinkedList<>() : new LinkedList<>(extraOrigins)); - return model; - } - - static MapWebAuthnPolicyEntity defaultWebAuthnPolicy() { - MapWebAuthnPolicyEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapWebAuthnPolicyEntity.class); - entity.setRpEntityName(Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME); - entity.setSignatureAlgorithms(Arrays.asList(Constants.DEFAULT_WEBAUTHN_POLICY_SIGNATURE_ALGORITHMS.split(","))); - entity.setRpId(""); - entity.setAttestationConveyancePreference(Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED); - entity.setAuthenticatorAttachment(Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED); - entity.setRequireResidentKey(Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED); - entity.setUserVerificationRequirement(Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED); - entity.setCreateTimeout(0); - entity.setAvoidSameAuthenticatorRegister(false); - entity.setAcceptableAaguids(new LinkedList<>()); - entity.setExtraOrigins(new LinkedList<>()); - return entity; - } - - String getRpEntityName(); - void setRpEntityName(String rpEntityName); - - List getSignatureAlgorithms(); - void setSignatureAlgorithms(List signatureAlgorithms); - - String getRpId(); - void setRpId(String rpId); - - String getAttestationConveyancePreference(); - void setAttestationConveyancePreference(String attestationConveyancePreference); - - String getAuthenticatorAttachment(); - void setAuthenticatorAttachment(String authenticatorAttachment); - - String getRequireResidentKey(); - void setRequireResidentKey(String requireResidentKey); - - String getUserVerificationRequirement(); - void setUserVerificationRequirement(String userVerificationRequirement); - - Integer getCreateTimeout(); - void setCreateTimeout(Integer createTimeout); - - Boolean isAvoidSameAuthenticatorRegister(); - void setAvoidSameAuthenticatorRegister(Boolean avoidSameAuthenticatorRegister); - - List getAcceptableAaguids(); - void setAcceptableAaguids(List acceptableAaguids); - - List getExtraOrigins(); - void setExtraOrigins(List extraOrigins); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/role/AbstractRoleModel.java b/model/map/src/main/java/org/keycloak/models/map/role/AbstractRoleModel.java deleted file mode 100644 index 9b37dfd7420..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/role/AbstractRoleModel.java +++ /dev/null @@ -1,53 +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.role; - -import java.util.Objects; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.common.AbstractEntity; - -public abstract class AbstractRoleModel implements RoleModel { - - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractRoleModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RoleModel)) return false; - - RoleModel that = (RoleModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleAdapter.java b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleAdapter.java deleted file mode 100644 index 2e28488ebdc..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleAdapter.java +++ /dev/null @@ -1,151 +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.role; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Stream; -import org.jboss.logging.Logger; -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RoleModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleContainerModel; -import org.keycloak.models.utils.KeycloakModelUtils; - -public class MapRoleAdapter extends AbstractRoleModel implements RoleModel { - - private static final Logger LOG = Logger.getLogger(MapRoleAdapter.class); - - public MapRoleAdapter(KeycloakSession session, RealmModel realm, MapRoleEntity entity) { - super(session, realm, entity); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public String getDescription() { - return entity.getDescription(); - } - - @Override - public void setDescription(String description) { - entity.setDescription(description); - } - - @Override - public void setName(String name) { - entity.setName(name); - } - - @Override - public boolean isComposite() { - return ! (entity.getCompositeRoles() == null || entity.getCompositeRoles().isEmpty()); - } - - @Override - public Stream getCompositesStream() { - Set compositeRoles = entity.getCompositeRoles() == null ? Collections.emptySet() : entity.getCompositeRoles(); - LOG.tracef("%% %s(%s).getCompositesStream():%d - %s", entity.getName(), entity.getId(), compositeRoles.size(), getShortStackTrace()); - return compositeRoles.stream() - .map(uuid -> session.roles().getRoleById(realm, uuid)) - .filter(Objects::nonNull); - } - - @Override - public Stream getCompositesStream(String search, Integer first, Integer max) { - Set compositeRoles = entity.getCompositeRoles() == null ? Collections.emptySet() : entity.getCompositeRoles(); - LOG.tracef("%% (%s).getCompositesStream(%s, %d, %d):%d - %s", this, search, first, max, compositeRoles.size(), getShortStackTrace()); - return session.roles().getRolesStream(realm, compositeRoles.stream(), search, first, max); - } - - @Override - public void addCompositeRole(RoleModel role) { - LOG.tracef("(%s).addCompositeRole(%s(%s))%s", this, role.getName(), role.getId(), getShortStackTrace()); - entity.addCompositeRole(role.getId()); - } - - @Override - public void removeCompositeRole(RoleModel role) { - LOG.tracef("(%s).removeCompositeRole(%s(%s))%s", this, role.getName(), role.getId(), getShortStackTrace()); - entity.removeCompositeRole(role.getId()); - } - - @Override - public boolean isClientRole() { - return entity.getClientId() != null; - } - - @Override - public String getContainerId() { - return isClientRole() ? entity.getClientId() : entity.getRealmId(); - } - - @Override - public RoleContainerModel getContainer() { - return isClientRole() ? session.clients().getClientById(realm, entity.getClientId()) : realm; - } - - @Override - public void setAttribute(String name, List values) { - entity.setAttribute(name, values); - } - - @Override - public void setSingleAttribute(String name, String value) { - setAttribute(name, Collections.singletonList(value)); - } - - @Override - public void removeAttribute(String name) { - entity.removeAttribute(name); - } - - @Override - public Map> getAttributes() { - Map> attributes = entity.getAttributes(); - return attributes == null ? Collections.emptyMap() : attributes; - } - - @Override - public boolean hasRole(RoleModel role) { - return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>()); - } - - @Override - public Stream getAttributeStream(String name) { - return getAttributes().getOrDefault(name, Collections.EMPTY_LIST).stream(); - } - - @Override - public String toString() { - return String.format("%s@%08x", getName(), System.identityHashCode(this)); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java deleted file mode 100644 index 9843da687d8..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java +++ /dev/null @@ -1,69 +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.role; - -import java.util.Set; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity" -) -@DeepCloner.Root -public interface MapRoleEntity extends AbstractEntity, UpdatableEntity, EntityWithAttributes { - - public abstract class AbstractRoleEntity extends UpdatableEntity.Impl implements MapRoleEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getRealmId(); - - String getClientId(); - - String getName(); - - String getDescription(); - - void setRealmId(String realmId); - - void setClientId(String clientId); - - void setName(String name); - - void setDescription(String description); - - Set getCompositeRoles(); - void setCompositeRoles(Set compositeRoles); - void addCompositeRole(String roleId); - void removeCompositeRole(String roleId); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java deleted file mode 100644 index b24bd4b1af3..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java +++ /dev/null @@ -1,303 +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.role; - -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Stream; -import org.jboss.logging.Logger; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.RoleModel.SearchableFields; -import org.keycloak.models.RoleProvider; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -public class MapRoleProvider implements RoleProvider { - - private static final Logger LOG = Logger.getLogger(MapRoleProvider.class); - private final KeycloakSession session; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapRoleProvider(KeycloakSession session, MapStorage roleStore) { - this.session = session; - this.store = roleStore; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return origEntity -> new MapRoleAdapter(session, realm, origEntity); - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - @Override - public RoleModel addRealmRole(RealmModel realm, String id, String name) { - if (getRealmRole(realm, name) != null) { - throw new ModelDuplicateException("Role with the same name exists: " + name + " for realm " + realm.getName()); - } - - LOG.tracef("addRealmRole(%s, %s, %s)%s", realm, id, name, getShortStackTrace()); - - MapRoleEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapRoleEntity.class); - entity.setId(id); - entity.setRealmId(realm.getId()); - entity.setName(name); - if (entity.getId() != null && storeWithRealm(realm).exists(entity.getId())) { - throw new ModelDuplicateException("Role exists: " + id); - } - entity = storeWithRealm(realm).create(entity); - return entityToAdapterFunc(realm).apply(entity); - } - - @Override - public Stream getRealmRolesStream(RealmModel realm, Integer first, Integer max) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - // filter realm roles only - .compare(SearchableFields.CLIENT_ID, Operator.NOT_EXISTS); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream getRolesStream(RealmModel realm, Stream ids, String search, Integer first, Integer max) { - LOG.tracef("getRolesStream(%s, %s, %s, %d, %d)%s", realm, ids, search, first, max, getShortStackTrace()); - if (ids == null) return Stream.empty(); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(RoleModel.SearchableFields.ID, Operator.IN, ids) - .compare(RoleModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - if (search != null) { - mcb = mcb.compare(RoleModel.SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); - } - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(first, max, RoleModel.SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream getRealmRolesStream(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - // filter realm roles only - .compare(SearchableFields.CLIENT_ID, Operator.NOT_EXISTS); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public RoleModel addClientRole(ClientModel client, String id, String name) { - if (getClientRole(client, name) != null) { - throw new ModelDuplicateException("Role with the same name exists: " + name + " for client " + client.getClientId()); - } - - LOG.tracef("addClientRole(%s, %s, %s)%s", client, id, name, getShortStackTrace()); - - MapRoleEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapRoleEntity.class); - final RealmModel realm = client.getRealm(); - entity.setId(id); - entity.setRealmId(realm.getId()); - entity.setName(name); - entity.setClientId(client.getId()); - if (entity.getId() != null && storeWithRealm(realm).exists(entity.getId())) { - throw new ModelDuplicateException("Role exists: " + id); - } - entity = storeWithRealm(realm).create(entity); - return entityToAdapterFunc(realm).apply(entity); - } - - @Override - public Stream getClientRolesStream(ClientModel client, Integer first, Integer max) { - DefaultModelCriteria mcb = criteria(); - final RealmModel realm = client.getRealm(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream getClientRolesStream(ClientModel client) { - DefaultModelCriteria mcb = criteria(); - final RealmModel realm = client.getRealm(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) - .map(entityToAdapterFunc(realm)); - } - @Override - public boolean removeRole(RoleModel role) { - LOG.tracef("removeRole(%s)%s", role, getShortStackTrace()); - - RealmModel realm = role.isClientRole() ? ((ClientModel)role.getContainer()).getRealm() : (RealmModel)role.getContainer(); - - session.invalidate(ROLE_BEFORE_REMOVE, realm, role); - - storeWithRealm(realm).delete(role.getId()); - - session.invalidate(ROLE_AFTER_REMOVE, realm, role); - - return true; - } - - @Override - public void removeRoles(RealmModel realm) { - getRealmRolesStream(realm).forEach(this::removeRole); - } - - @Override - public void removeRoles(ClientModel client) { - getClientRolesStream(client).forEach(this::removeRole); - } - - @Override - public RoleModel getRealmRole(RealmModel realm, String name) { - if (name == null) { - return null; - } - LOG.tracef("getRealmRole(%s, %s)%s", realm, name, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - // filter realm roles only - .compare(SearchableFields.CLIENT_ID, Operator.NOT_EXISTS) - .compare(SearchableFields.NAME, Operator.EQ, name); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(entityToAdapterFunc(realm)) - .findFirst() - .orElse(null); - } - - @Override - public RoleModel getClientRole(ClientModel client, String name) { - if (name == null) { - return null; - } - LOG.tracef("getClientRole(%s, %s)%s", client, name, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - final RealmModel realm = client.getRealm(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()) - .compare(SearchableFields.NAME, Operator.EQ, name); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(entityToAdapterFunc(realm)) - .findFirst() - .orElse(null); - } - - @Override - public RoleModel getRoleById(RealmModel realm, String id) { - if (id == null || realm == null || realm.getId() == null) { - return null; - } - - LOG.tracef("getRoleById(%s, %s)%s", realm, id, getShortStackTrace()); - - MapRoleEntity entity = storeWithRealm(realm).read(id); - String realmId = realm.getId(); - // when a store doesn't store information about all realms, it doesn't have the information about - return (entity == null || (entity.getRealmId() != null && !Objects.equals(realmId, entity.getRealmId()))) - ? null - : entityToAdapterFunc(realm).apply(entity); - } - - @Override - public Stream searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) { - if (search == null) { - return Stream.empty(); - } - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - // filter realm roles only - .compare(SearchableFields.CLIENT_ID, Operator.NOT_EXISTS) - .or( - mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"), - mcb.compare(SearchableFields.DESCRIPTION, Operator.ILIKE, "%" + search + "%") - ); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) { - if (search == null) { - return Stream.empty(); - } - DefaultModelCriteria mcb = criteria(); - final RealmModel realm = client.getRealm(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()) - .or( - mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"), - mcb.compare(SearchableFields.DESCRIPTION, Operator.ILIKE, "%" + search + "%") - ); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) - .map(entityToAdapterFunc(realm)); - } - - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - public void preRemove(RealmModel realm, RoleModel role) { - // Remove reference from all composite roles - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.COMPOSITE_ROLE, Operator.EQ, role.getId()); - storeWithRealm(realm).read(withCriteria(mcb)).forEach(mapRoleEntity -> mapRoleEntity.removeCompositeRole(role.getId())); - } - - @Override - public void close() { - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProviderFactory.java deleted file mode 100644 index 8271a5d0cab..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProviderFactory.java +++ /dev/null @@ -1,64 +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.role; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleContainerModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.RoleProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE; - -public class MapRoleProviderFactory extends AbstractMapProviderFactory implements RoleProviderFactory, InvalidationHandler { - - public MapRoleProviderFactory() { - super(RoleModel.class, MapRoleProvider.class); - } - - @Override - public MapRoleProvider createNew(KeycloakSession session) { - return new MapRoleProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "Role provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0]); - } else if (type == CLIENT_BEFORE_REMOVE) { - create(session).removeRoles((ClientModel) params[1]); - } else if (type == ROLE_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]); - } else if (type == ROLE_AFTER_REMOVE) { - session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() { - @Override public RoleModel getRole() { return (RoleModel) params[1]; } - @Override public KeycloakSession getKeycloakSession() { return session; } - }); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/AbstractSingleUseObjectModel.java b/model/map/src/main/java/org/keycloak/models/map/singleUseObject/AbstractSingleUseObjectModel.java deleted file mode 100644 index 0e29cfcfb5c..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/AbstractSingleUseObjectModel.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 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.singleUseObject; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -/** - * @author Martin Kanis - */ -public abstract class AbstractSingleUseObjectModel implements SingleUseObjectValueModel { - - protected final KeycloakSession session; - protected final E entity; - - public AbstractSingleUseObjectModel(KeycloakSession session, E entity) { - Objects.requireNonNull(entity, "entity"); - - this.session = session; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SingleUseObjectValueModel)) return false; - - MapSingleUseObjectAdapter that = (MapSingleUseObjectAdapter) o; - return Objects.equals(that.entity.getId(), entity.getId()); - } - - @Override - public int hashCode() { - return entity.getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectAdapter.java b/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectAdapter.java deleted file mode 100644 index b8146c2f41e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectAdapter.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022 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.singleUseObject; - -import org.keycloak.models.KeycloakSession; - -import java.util.Collections; -import java.util.Map; - -/** - * @author Martin Kanis - */ -public class MapSingleUseObjectAdapter extends AbstractSingleUseObjectModel { - - public MapSingleUseObjectAdapter(KeycloakSession session, MapSingleUseObjectEntity entity) { - super(session, entity); - } - - @Override - public Map getNotes() { - Map notes = entity.getNotes(); - return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); - } - - @Override - public String getNote(String name) { - Map notes = entity.getNotes(); - return notes == null ? null : notes.get(name); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectEntity.java b/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectEntity.java deleted file mode 100644 index 78791c1aa75..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectEntity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 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.singleUseObject; - -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Map; - -/** - * @author Martin Kanis - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity.AbstractSingleUseObjectEntity" -) -@DeepCloner.Root -public interface MapSingleUseObjectEntity extends AbstractEntity, UpdatableEntity, ExpirableEntity { - - public abstract class AbstractSingleUseObjectEntity extends UpdatableEntity.Impl implements MapSingleUseObjectEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - String getObjectKey(); - void setObjectKey(String objectKey); - - Map getNotes(); - void setNotes(Map notes); - String getNote(String name); - void setNote(String key, String value); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectProvider.java b/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectProvider.java deleted file mode 100644 index 21c3e80ee18..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectProvider.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2022 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.singleUseObject; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.SingleUseObjectProvider; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.Collections; -import java.util.Map; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -/** - * @author Martin Kanis - */ -public class MapSingleUseObjectProvider implements SingleUseObjectProvider { - - private static final Logger LOG = Logger.getLogger(MapSingleUseObjectProvider.class); - protected final MapStorage singleUseObjectTx; - - public MapSingleUseObjectProvider(MapStorage storage) { - this.singleUseObjectTx = storage; - } - - @Override - public void put(String key, long lifespanSeconds, Map notes) { - LOG.tracef("put(%s)%s", key, getShortStackTrace()); - - MapSingleUseObjectEntity singleUseEntity = getWithExpiration(key); - - if (singleUseEntity != null) { - throw new ModelDuplicateException("Single-use object entity exists: " + singleUseEntity.getObjectKey()); - } - - singleUseEntity = DeepCloner.DUMB_CLONER.newInstance(MapSingleUseObjectEntity.class); - singleUseEntity.setObjectKey(key); - singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds)); - singleUseEntity.setNotes(notes); - - singleUseObjectTx.create(singleUseEntity); - } - - @Override - public Map get(String key) { - LOG.tracef("get(%s)%s", key, getShortStackTrace()); - - MapSingleUseObjectEntity singleUseObject = getWithExpiration(key); - if (singleUseObject != null) { - Map notes = singleUseObject.getNotes(); - return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); - } - - return null; - } - - @Override - public Map remove(String key) { - LOG.tracef("remove(%s)%s", key, getShortStackTrace()); - - MapSingleUseObjectEntity singleUseEntity = getWithExpiration(key); - - if (singleUseEntity != null) { - Map notes = singleUseEntity.getNotes(); - if (singleUseObjectTx.delete(singleUseEntity.getId())) { - return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); - } - } - // the single-use entity expired or someone else already used and deleted it - return null; - } - - @Override - public boolean replace(String key, Map notes) { - LOG.tracef("replace(%s)%s", key, getShortStackTrace()); - - MapSingleUseObjectEntity singleUseEntity = getWithExpiration(key); - if (singleUseEntity != null) { - singleUseEntity.setNotes(notes); - return true; - } - - return false; - } - - @Override - public boolean putIfAbsent(String key, long lifespanInSeconds) { - LOG.tracef("putIfAbsent(%s)%s", key, getShortStackTrace()); - - MapSingleUseObjectEntity singleUseEntity = getWithExpiration(key); - if (singleUseEntity != null) { - return false; - } else { - singleUseEntity = DeepCloner.DUMB_CLONER.newInstance(MapSingleUseObjectEntity.class); - singleUseEntity.setObjectKey(key); - singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds)); - - singleUseObjectTx.create(singleUseEntity); - - return true; - } - } - - @Override - public boolean contains(String key) { - LOG.tracef("contains(%s)%s", key, getShortStackTrace()); - - MapSingleUseObjectEntity singleUseObject = getWithExpiration(key); - - return singleUseObject != null; - } - - @Override - public void close() { - - } - - private MapSingleUseObjectEntity getWithExpiration(String key) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key); - - return singleUseObjectTx.read(withCriteria(mcb)) - .filter(entity -> { - if (isExpired(entity, false)) { - singleUseObjectTx.delete(entity.getId()); - return false; - } else { - return true; - } - }) - .findFirst().orElse(null); - } - } diff --git a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectProviderFactory.java deleted file mode 100644 index 9cf290b451b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/singleUseObject/MapSingleUseObjectProviderFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022 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.singleUseObject; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.SingleUseObjectProviderFactory; -import org.keycloak.models.map.common.AbstractMapProviderFactory; - -/** - * @author Martin Kanis - */ -public class MapSingleUseObjectProviderFactory extends AbstractMapProviderFactory - implements SingleUseObjectProviderFactory { - - public MapSingleUseObjectProviderFactory() { - super(SingleUseObjectValueModel.class, MapSingleUseObjectProvider.class); - } - - @Override - public MapSingleUseObjectProvider createNew(KeycloakSession session) { - return new MapSingleUseObjectProvider(getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "Single use object provider"; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/CriterionNotSupportedException.java b/model/map/src/main/java/org/keycloak/models/map/storage/CriterionNotSupportedException.java deleted file mode 100644 index cc3d5816e94..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/CriterionNotSupportedException.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.storage; - -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.storage.SearchableModelField; - -/** - * - * @author hmlnarik - */ -public class CriterionNotSupportedException extends RuntimeException { - private final SearchableModelField field; - private final Operator op; - - public CriterionNotSupportedException(SearchableModelField field, Operator op) { - super("Criterion not supported: operator: " + op + ", field: " + field); - this.field = field; - this.op = op; - } - - public CriterionNotSupportedException(SearchableModelField field, Operator op, String message) { - super(message); - this.field = field; - this.op = op; - } - - public CriterionNotSupportedException(SearchableModelField field, Operator op, String message, Throwable cause) { - super(message, cause); - this.field = field; - this.op = op; - } - - public SearchableModelField getField() { - return field; - } - - public Operator getOp() { - return op; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/CrudOperations.java b/model/map/src/main/java/org/keycloak/models/map/storage/CrudOperations.java deleted file mode 100644 index bfe8642400e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/CrudOperations.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2023 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.storage; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.QueryParameters; - - -import java.util.stream.Stream; - -/** - * Interface for CRUD operations on the storage. The operations may not respect transactional boundaries - * if the underlying storage does not support it. - * - * @param Type of the value stored in the storage - * @param Type of the model object - */ -public interface CrudOperations { - - /** - * Creates an object in the storage. - *
- * ID of the {@code value} may be prescribed in id of the {@code value}. - * If the id is {@code null} or its format is not matching the store internal format for ID, then - * the {@code value}'s ID will be generated and returned in the id of the return value. - * - * @param value Entity to create in the store - * @throws NullPointerException if {@code value} is {@code null} - * @see AbstractEntity#getId() - * @return Entity representing the {@code value} in the store. It may or may not be the same instance as {@code value} - */ - V create(V value); - - /** - * Returns object with the given {@code key} from the storage or {@code null} if object does not exist. - *
- * If {@code V} implements {@link org.keycloak.models.map.common.ExpirableEntity} this method should not return - * entities that are expired. See {@link org.keycloak.models.map.common.ExpirableEntity} JavaDoc for more details. - * - * TODO: Consider returning {@code Optional} instead. - * @param key Key of the object. Must not be {@code null}. - * @return See description - * @throws NullPointerException if the {@code key} is {@code null} - */ - V read(String key); - - /** - * Updates the object with the key of the {@code value}'s ID in the storage if it already exists. - * - * @param value Updated value - * @return the previous value associated with the specified key, or null if there was no mapping for the key. - * (A null return can also indicate that the map previously associated null with the key, - * if the implementation supports null values.) - * @throws NullPointerException if the object or its {@code id} is {@code null} - * @see AbstractEntity#getId() - */ - V update(V value); - - /** - * Deletes object with the given {@code key} from the storage, if exists, no-op otherwise. - * @param key - * @return Returns {@code true} if the object has been deleted or result cannot be determined, {@code false} otherwise. - */ - boolean delete(String key); - - /** - * Deletes objects that match the given criteria. - * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. - * @return Number of removed objects (might return {@code -1} if not supported) - */ - long delete(QueryParameters queryParameters); - - /** - * Returns stream of objects satisfying given {@code criteria} from the storage. - * The criteria are specified in the given criteria builder based on model properties. - *
- * If {@code V} implements {@link org.keycloak.models.map.common.ExpirableEntity} this method should not return - * entities that are expired. See {@link org.keycloak.models.map.common.ExpirableEntity} JavaDoc for more details. - * - * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. - * @return Stream of objects. Never returns {@code null}. - */ - Stream read(QueryParameters queryParameters); - - /** - * Returns the number of objects satisfying given {@code criteria} from the storage. - * The criteria are specified in the given criteria builder based on model properties. - * - * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. - * @return Number of objects. Never returns {@code null}. - */ - long getCount(QueryParameters queryParameters); - - /** - * Returns {@code true} if the object with the given {@code key} exists in the storage. {@code false} otherwise. - * - * @param key Key of the object. Must not be {@code null}. - * @return See description - * @throws NullPointerException if the {@code key} is {@code null} - */ - default boolean exists(String key) { - return read(key) != null; - } - - /** - * Returns {@code true} if at least one object is satisfying given {@code criteria} from the storage. {@code false} otherwise. - * The criteria are specified in the given criteria builder based on model properties. - * - * @param queryParameters parameters for the query - * @return See description - */ - default boolean exists(QueryParameters queryParameters) { - return getCount(queryParameters) > 0; - } - - /** - * Determines first available key from the value upon creation. - * @param value - * @return - */ - default String determineKeyFromValue(V value) { - return value == null ? null : value.getId(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java deleted file mode 100644 index a74b27404f6..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java +++ /dev/null @@ -1,123 +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.storage; - -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.stream.Stream; - -/** - * A storage for entities that is based on a map and operates in the context of transaction - * managed by current {@code KeycloakSession}. Implementations of its methods should respect - * transactional boundaries of that transaction. - */ -public interface MapStorage { - - /** - * Instructs this storage to add a new value into the underlying store on commit in the context of the current transaction. - *

- * Updates to the returned instances of {@code V} would be visible in the current transaction - * and will propagate into the underlying store upon commit. - * - * The ID of the entity passed in the parameter might change to a different value in the returned value - * if the underlying storage decided this was necessary. - * If the ID of the entity was null before, it will be set on the returned value. - * - * @param value the value - * @return Entity representing the {@code value} in the store. It may or may not be the same instance as {@code value}. - */ - V create(V value); - - /** - * Provides possibility to lookup for values by a {@code key} in the underlying store with respect to changes done - * in current transaction. Updates to the returned instance would be visible in the current transaction - * and will propagate into the underlying store upon commit. - * - * If {@code V} implements {@link org.keycloak.models.map.common.ExpirableEntity} this method should not return - * entities that are expired. See {@link org.keycloak.models.map.common.ExpirableEntity} JavaDoc for more details. - * - * @param key identifier of a value - * @return a value associated with the given {@code key} - */ - V read(String key); - - /** - * Returns a stream of values from underlying storage that are updated based on the current transaction changes; - * i.e. the result contains updates and excludes of records that have been created, updated or deleted in this - * transaction by respective methods of this interface. - *

- * Updates to the returned instances of {@code V} would be visible in the current transaction - * and will propagate into the underlying store upon commit. - * - * If {@code V} implements {@link org.keycloak.models.map.common.ExpirableEntity} this method should not return - * entities that are expired. See {@link org.keycloak.models.map.common.ExpirableEntity} JavaDoc for more details. - * - * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. - * @return values that fulfill the given criteria, that are updated based on changes in the current transaction - */ - Stream read(QueryParameters queryParameters); - - /** - * Returns a number of values present in the underlying storage that fulfill the given criteria with respect to - * changes done in the current transaction. - * - * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. - * @return number of values present in the storage that fulfill the given criteria - */ - long getCount(QueryParameters queryParameters); - - /** - * Instructs this storage to delete a value associated with the identifier {@code key} from the underlying store - * upon commit. - * - * @return Returns {@code true} if the object has been deleted or result cannot be determined, {@code false} otherwise. - * @param key identifier of a value - */ - boolean delete(String key); - - /** - * Instructs this transaction to remove values (identified by {@code mcb} filter) from the underlying store upon commit. - * - * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. - * @return number of removed objects (might return {@code -1} if not supported) - */ - long delete(QueryParameters queryParameters); - - /** - * Returns {@code true} if the object with the given {@code key} exists in the underlying storage with respect to changes done - * in the current transaction. {@code false} otherwise. - * - * @param key Key of the object. Must not be {@code null}. - * @return See description - * @throws NullPointerException if the {@code key} is {@code null} - */ - default boolean exists(String key) { - return read(key) != null; - } - - /** - * Returns {@code true} if at least one object is satisfying given {@code criteria} from the underlying storage with respect to changes done - * in the current transaction. {@code false} otherwise. - * The criteria are specified in the given criteria builder based on model properties. - * - * @param queryParameters parameters for the query - * @return See description - */ - default boolean exists(QueryParameters queryParameters) { - return getCount(queryParameters) > 0; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProvider.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProvider.java deleted file mode 100644 index a8ad8550f17..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProvider.java +++ /dev/null @@ -1,40 +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.storage; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag; -import org.keycloak.provider.Provider; - -/** - * - * @author hmlnarik - */ -public interface MapStorageProvider extends Provider { - - /** - * Returns a key-value storage implementation for the given types. - * - * @param type of the value - * @param type of the corresponding model (e.g. {@code UserModel}) - * @param modelType Model type - * @param flags Flags of the returned storage. Best effort, flags may be not honored by underlying implementation - * @return - * @throws IllegalArgumentException If some of the types is not supported by the underlying implementation. - */ - MapStorage getMapStorage(Class modelType, Flag... flags); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java deleted file mode 100644 index 0cb164011cd..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java +++ /dev/null @@ -1,32 +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.storage; - -import org.keycloak.component.ComponentFactory; -import org.keycloak.provider.ProviderFactory; - -/** - * - * @author hmlnarik - */ -public interface MapStorageProviderFactory extends ProviderFactory, ComponentFactory { - - public enum Flag { - INITIALIZE_EMPTY, - LOCAL - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java deleted file mode 100644 index 685026716d8..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java +++ /dev/null @@ -1,51 +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.storage; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * - * @author hmlnarik - */ -public class MapStorageSpi implements Spi { - - public static final String NAME = "mapStorage"; - - @Override - public boolean isInternal() { - return false; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return MapStorageProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return MapStorageProviderFactory.class; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageWithAuth.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageWithAuth.java deleted file mode 100644 index c957b005775..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageWithAuth.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 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.storage; - -import org.keycloak.credential.CredentialInput; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.user.MapCredentialValidationOutput; - -/** - * A map store that can authenticate the credentials provided by a user. - * - * @author Alexander Schwartz - */ -public interface MapStorageWithAuth extends MapStorage { - - /** - * Authenticate a user with the provided input credentials. Use this, for example, for Kerberos SPNEGO - * authentication, where the user will be determined at the end of the interaction with the client. - * @param realm realm against which to authenticate against - * @param input information provided by the user - * @return Information on how to continue the conversion with the client, or a terminal result. For a successful - * authentication, will also contain information about the user. - */ - MapCredentialValidationOutput authenticate(RealmModel realm, CredentialInput input); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/ModelCriteriaBuilder.java b/model/map/src/main/java/org/keycloak/models/map/storage/ModelCriteriaBuilder.java deleted file mode 100644 index 371dfc0f9f1..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/ModelCriteriaBuilder.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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.storage; - -import org.keycloak.storage.SearchableModelField; - -/** - * Builder for criteria that can be used to limit results obtained from the store. - * This class is used for similar purpose as e.g. JPA's {@code CriteriaBuilder}, - * however it is much simpler version as it is tailored to very specific needs - * of future Keycloak store. - *

- * Implementations are expected to be immutable. The expected use is like this: - *

- * cb = storage.getCriteriaBuilder();
- * storage.read(
- *   cb.or(
- *     cb.compare(FIELD1, EQ, 1).compare(FIELD2, EQ, 2),
- *     cb.compare(FIELD1, EQ, 3).compare(FIELD2, EQ, 4)
- *   )
- * );
- * 
- * The above code should read items where - * {@code (FIELD1 == 1 && FIELD2 == 2) || (FIELD1 == 3 && FIELD2 == 4)}. - * - *

- * It is equivalent to this: - *

- * cb = storage.getCriteriaBuilder();
- * storage.read(
- *   cb.or(
- *     cb.and(cb.compare(FIELD1, EQ, 1), cb.compare(FIELD2, EQ, 2)),
- *     cb.and(cb.compare(FIELD1, EQ, 3), cb.compare(FIELD2, EQ, 4))
- *   )
- * );
- * 
- * - * @author hmlnarik - */ -public interface ModelCriteriaBuilder> { - - /** - * The operators are very basic ones for this use case. In the real scenario, - * new operators can be added, possibly with different arity, e.g. {@code IN}. - * The {@link ModelCriteriaBuilder#compare} method would need an adjustment - * then, likely to taky vararg {@code value} instead of single value as it - * is now. - */ - public enum Operator { - /** Equals to */ - EQ, - /** Not equals to */ - NE, - /** Less than */ - LT, - /** Less than or equal */ - LE, - /** Greater than */ - GT, - /** Greater than or equal */ - GE, - /** Similar to SQL case-sensitive LIKE Whole string is matched. - * Percent sign means "any characters", question mark means "any single character": - *
    - *
  • {@code field LIKE "abc"} means value of the field {@code field} must match exactly {@code abc}
  • - *
  • {@code field LIKE "abc%"} means value of the field {@code field} must start with {@code abc}
  • - *
  • {@code field LIKE "%abc"} means value of the field {@code field} must end with {@code abc}
  • - *
  • {@code field LIKE "%abc%"} means value of the field {@code field} must contain {@code abc}
  • - *
- */ - LIKE, - /** - * Similar to SQL case-insensitive LIKE. Whole string is matched. - * Percent sign means "any characters", question mark means "any single character": - *
    - *
  • {@code field ILIKE "abc"} means value of the field {@code field} must match exactly {@code abc}, {@code ABC}, {@code aBc} etc.
  • - *
  • {@code field ILIKE "abc%"} means value of the field {@code field} must start with {@code abc}, {@code ABC}, {@code aBc} etc.
  • - *
  • {@code field ILIKE "%abc"} means value of the field {@code field} must end with {@code abc}, {@code ABC}, {@code aBc} etc.
  • - *
  • {@code field ILIKE "%abc%"} means value of the field {@code field} must contain {@code abc}, {@code ABC}, {@code aBc} etc.
  • - *
- */ - ILIKE, - /** - * Operator for belonging into a collection of values. Operand in {@code value} - * can be an array (via an implicit conversion of the vararg), a {@link java.util.Collection} or a {@link java.util.stream.Stream}. - */ - IN, - /** Is not null and, in addition, in case of collection not empty */ - EXISTS, - /** Is null or, in addition, in case of collection empty */ - NOT_EXISTS, - } - - /** - * Adds a constraint for the given model field to this criteria builder - * and returns a criteria builder that is combined with the the new constraint. - * The resulting constraint is a logical conjunction (i.e. AND) of the original - * constraint present in this {@link ModelCriteriaBuilder} and the given operator. - * - * @param modelField Field on the logical model to be constrained - * @param op Operator - * @param value Additional operands of the operator. - * @return - * @throws CriterionNotSupportedException If the operator is not supported for the given field. - */ - Self compare(SearchableModelField modelField, Operator op, Object... value); - - /** - * Creates and returns a new instance of {@code ModelCriteriaBuilder} that - * combines the given builders with the Boolean AND operator. - *

- * Predicate coming out of {@code and} on an empty array of {@code builders} - * (i.e. empty conjunction) is always {@code true}. - * - *

-     *   cb = storage.getCriteriaBuilder();
-     *   storage.read(cb.or(
-     *     cb.and(cb.compare(FIELD1, EQ, 1), cb.compare(FIELD2, EQ, 2)),
-     *     cb.and(cb.compare(FIELD1, EQ, 3), cb.compare(FIELD2, EQ, 4))
-     *   );
-     * 
- * - * @throws CriterionNotSupportedException If the operator is not supported for the given field. - */ - @SuppressWarnings("unchecked") - Self and(Self... builders); - - /** - * Creates and returns a new instance of {@code ModelCriteriaBuilder} that - * combines the given builders with the Boolean OR operator. - *

- * Predicate coming out of {@code or} on an empty array of {@code builders} - * (i.e. empty disjunction) is always {@code false}. - * - *

-     *   cb = storage.getCriteriaBuilder();
-     *   storage.read(cb.or(
-     *     cb.compare(FIELD1, EQ, 1).compare(FIELD2, EQ, 2),
-     *     cb.compare(FIELD1, EQ, 3).compare(FIELD2, EQ, 4)
-     *   );
-     * 
- * - * @throws CriterionNotSupportedException If the operator is not supported for the given field. - */ - @SuppressWarnings("unchecked") - Self or(Self... builders); - - /** - * Creates and returns a new instance of {@code ModelCriteriaBuilder} that - * negates the given builder. - *

- * Note that if the {@code builder} has no condition yet, there is nothing - * to negate: empty negation is always {@code true}. - * - * @param builder - * @return - * @throws CriterionNotSupportedException If the operator is not supported for the given field. - */ - Self not(Self builder); - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java b/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java deleted file mode 100644 index 53b2664af36..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * 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.storage; - -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.events.Event; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.lock.MapLockEntity; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.role.MapRoleEntityFields; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity; -import org.keycloak.sessions.RootAuthenticationSessionModel; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import static org.keycloak.models.map.common.AutogeneratedClasses.ENTITY_FIELDS; - -/** - * Utility class covering various aspects of relationship between model and entity classes. - * @author hmlnarik - */ -public class ModelEntityUtil { - - private static final Map, String> MODEL_TO_NAME = new IdentityHashMap<>(); - static { - MODEL_TO_NAME.put(SingleUseObjectValueModel.class, "single-use-objects"); - MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes"); - MODEL_TO_NAME.put(ClientModel.class, "clients"); - MODEL_TO_NAME.put(GroupModel.class, "groups"); - MODEL_TO_NAME.put(RealmModel.class, "realms"); - MODEL_TO_NAME.put(RoleModel.class, "roles"); - MODEL_TO_NAME.put(RootAuthenticationSessionModel.class, "auth-sessions"); - MODEL_TO_NAME.put(UserLoginFailureModel.class, "user-login-failures"); - MODEL_TO_NAME.put(UserModel.class, "users"); - MODEL_TO_NAME.put(UserSessionModel.class, "user-sessions"); - - // authz - MODEL_TO_NAME.put(PermissionTicket.class, "authz-permission-tickets"); - MODEL_TO_NAME.put(Policy.class, "authz-policies"); - MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers"); - MODEL_TO_NAME.put(Resource.class, "authz-resources"); - MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes"); - - // events - MODEL_TO_NAME.put(AdminEvent.class, "admin-events"); - MODEL_TO_NAME.put(Event.class, "auth-events"); - - // locks - MODEL_TO_NAME.put(MapLockEntity.class, "locks"); - } - private static final Map> NAME_TO_MODEL = MODEL_TO_NAME.entrySet().stream().collect(Collectors.toUnmodifiableMap(Entry::getValue, Entry::getKey)); - - private static final Map, Class> MODEL_TO_ENTITY_TYPE = new IdentityHashMap<>(); - static { - MODEL_TO_ENTITY_TYPE.put(SingleUseObjectValueModel.class, MapSingleUseObjectEntity.class); - MODEL_TO_ENTITY_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class); - MODEL_TO_ENTITY_TYPE.put(ClientModel.class, MapClientEntity.class); - MODEL_TO_ENTITY_TYPE.put(GroupModel.class, MapGroupEntity.class); - MODEL_TO_ENTITY_TYPE.put(RealmModel.class, MapRealmEntity.class); - MODEL_TO_ENTITY_TYPE.put(RoleModel.class, MapRoleEntity.class); - MODEL_TO_ENTITY_TYPE.put(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionEntity.class); - MODEL_TO_ENTITY_TYPE.put(UserLoginFailureModel.class, MapUserLoginFailureEntity.class); - MODEL_TO_ENTITY_TYPE.put(UserModel.class, MapUserEntity.class); - MODEL_TO_ENTITY_TYPE.put(UserSessionModel.class, MapUserSessionEntity.class); - MODEL_TO_ENTITY_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.class); - - // authz - MODEL_TO_ENTITY_TYPE.put(PermissionTicket.class, MapPermissionTicketEntity.class); - MODEL_TO_ENTITY_TYPE.put(Policy.class, MapPolicyEntity.class); - MODEL_TO_ENTITY_TYPE.put(ResourceServer.class, MapResourceServerEntity.class); - MODEL_TO_ENTITY_TYPE.put(Resource.class, MapResourceEntity.class); - MODEL_TO_ENTITY_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class); - - // events - MODEL_TO_ENTITY_TYPE.put(AdminEvent.class, MapAdminEventEntity.class); - MODEL_TO_ENTITY_TYPE.put(Event.class, MapAuthEventEntity.class); - } - private static final Map, Class> ENTITY_TO_MODEL_TYPE = MODEL_TO_ENTITY_TYPE.entrySet().stream().collect(Collectors.toUnmodifiableMap(Entry::getValue, Entry::getKey)); - private static final String ID_FIELD_NAME = MapRoleEntityFields.ID.getName(); - private static final Map, EntityField> ENTITY_TO_ID_FIELD = ENTITY_FIELDS.entrySet().stream() - .filter(me -> Stream.of(me.getValue()).anyMatch(e -> ID_FIELD_NAME.equals(e.getName()))) - .map(me -> Map.entry(me.getKey(), Stream.of(me.getValue()).filter(e -> ID_FIELD_NAME.equals(e.getName())).findAny().orElse(null))) - .filter(me -> me.getValue() != null) - .collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue)); - - private static final String REALM_ID_FIELD_NAME = MapRoleEntityFields.REALM_ID.getName(); - private static final Map, EntityField> ENTITY_TO_REALM_ID_FIELD = ENTITY_FIELDS.entrySet().stream() - .filter(me -> Stream.of(me.getValue()).anyMatch(e -> REALM_ID_FIELD_NAME.equals(e.getName()))) - .map(me -> Map.entry(me.getKey(), Stream.of(me.getValue()).filter(e -> REALM_ID_FIELD_NAME.equals(e.getName())).findAny().orElse(null))) - .filter(me -> me.getValue() != null) - .collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue)); - - @SuppressWarnings("unchecked") - public static Class getEntityType(Class modelClass) { - return (Class) MODEL_TO_ENTITY_TYPE.get(modelClass); - } - - @SuppressWarnings("unchecked") - public static Class getEntityType(Class modelClass, Class defaultClass) { - return (Class) MODEL_TO_ENTITY_TYPE.getOrDefault(modelClass, defaultClass); - } - - @SuppressWarnings("unchecked") - public static Class getModelType(Class entityClass) { - return (Class) ENTITY_TO_MODEL_TYPE.get(entityClass); - } - - @SuppressWarnings("unchecked") - public static Class getModelType(Class entityClass, Class defaultClass) { - return (Class) ENTITY_TO_MODEL_TYPE.getOrDefault(entityClass, defaultClass); - } - - public static String getModelName(Class key, String defaultValue) { - return MODEL_TO_NAME.getOrDefault(key, defaultValue); - } - - public static String getModelName(Class key) { - return MODEL_TO_NAME.get(key); - } - - public static Set getModelNames() { - return NAME_TO_MODEL.keySet(); - } - - @SuppressWarnings("unchecked") - public static Class getModelClass(String key) { - return (Class) NAME_TO_MODEL.get(key); - } - - @SuppressWarnings("unchecked") - public static boolean entityFieldsKnown(Class entityClass) { - return ENTITY_FIELDS.containsKey(entityClass); - } - - @SuppressWarnings("unchecked") - public static Stream> getEntityFields(Class entityClass) { - EntityField[] values = (EntityField[]) ENTITY_FIELDS.get(entityClass); - return values == null ? Stream.empty() : Stream.of(values); - } - - public static Optional> getEntityField(Class entityClass, String fieldNameCamelCase) { - final Stream> s = getEntityFields(entityClass); - - return s - .filter(ef -> fieldNameCamelCase.equals(ef.getNameCamelCase())) - .findAny(); - } - - @SuppressWarnings("unchecked") - public static EntityField getIdField(Class targetEntityClass) { - return (EntityField) ENTITY_TO_ID_FIELD.get(targetEntityClass); - } - - @SuppressWarnings("unchecked") - public static EntityField getRealmIdField(Class targetEntityClass) { - return (EntityField) ENTITY_TO_REALM_ID_FIELD.get(targetEntityClass); - } - - public static T supplyReadOnlyFieldValueIfUnset(T entity, EntityField entityField, Object value) { - if (entity == null || Objects.equals(entityField.get(entity), value)) { - return entity; - } - return DeepCloner.DUMB_CLONER.entityFieldDelegate(entity, new EntityFieldDelegate.WithEntity<>(entity) { - @Override - public > & EntityField> Object get(EF field) { - if (field == entityField) { - return value; - } - return super.get(field); - } - - @Override - public > & EntityField> void set(EF field, V value) { - if (field != entityField) { - super.set(field, value); - } - } - - @Override - public String toString() { - return super.toString() + " [fixed " + entityField + "=" + value + "]"; - } - }); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java b/model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java deleted file mode 100644 index f520e5c1df4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.keycloak.models.map.storage; - -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.storage.SearchableModelField; - -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; - -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; - -/** - * Wraps together parameters for querying storage e.g. number of results to return, requested order or filtering criteria - * - * @param Provide entity specific type checking, for example, when we create {@code QueryParameters} - * instance for Users, M is equal to UserModel, hence we are not able, for example, to order result by a - * {@link SearchableModelField} defined for clients in {@link org.keycloak.models.ClientModel}. - */ -public class QueryParameters { - - private Integer offset; - private Integer limit; - private final List> orderBy = new LinkedList<>(); - private DefaultModelCriteria mcb; - - public QueryParameters() { - } - - public QueryParameters(DefaultModelCriteria mcb) { - this.mcb = mcb; - } - - /** - * Creates a new {@code QueryParameters} instance initialized with {@link ModelCriteriaBuilder} - * - * @param mcb filtering criteria - * @param model type - * @return a new {@code QueryParameters} instance - */ - public static QueryParameters withCriteria(DefaultModelCriteria mcb) { - return new QueryParameters<>(mcb); - } - - /** - * Sets pagination (offset, limit and orderBy) parameters to {@code QueryParameters} - * - * @param offset - * @param limit - * @param orderByAscField - * @return this object - */ - public QueryParameters pagination(Integer offset, Integer limit, SearchableModelField orderByAscField) { - this.offset = offset; - this.limit = limit; - this.orderBy.add(new OrderBy<>(orderByAscField, ASCENDING)); - - return this; - } - - /** - * Sets orderBy parameter; can be called repeatedly; fields are stored in a list where the first field has highest - * priority when determining order; e.g. the second field is compared only when values for the first field are equal - * - * @param searchableModelField - * @return this object - */ - public QueryParameters orderBy(SearchableModelField searchableModelField, Order order) { - orderBy.add(new OrderBy<>(searchableModelField, order)); - - return this; - } - - /** - * Sets offset parameter - * - * @param offset - * @return - */ - public QueryParameters offset(Integer offset) { - this.offset = offset; - return this; - } - - /** - * Sets limit parameter - * - * @param limit - * @return - */ - public QueryParameters limit(Integer limit) { - this.limit = limit; - return this; - } - - public Integer getOffset() { - return offset; - } - - public Integer getLimit() { - return limit; - } - - public DefaultModelCriteria getModelCriteriaBuilder() { - return mcb; - } - - public List> getOrderBy() { - return orderBy; - } - - @Override - public String toString() { - return "QueryParameters{" + - "offset=" + offset + - ", limit=" + limit + - ", orderBy=" + orderBy + - ", mcb=" + mcb + - '}'; - } - - /** - * Enum for ascending or descending ordering - */ - public enum Order { - ASCENDING, - DESCENDING - } - - /** - * Wrapper class for a field with its {@code Order}, ascending or descending - * - * @param - */ - public static class OrderBy { - private final SearchableModelField modelField; - private final Order order; - - public OrderBy(SearchableModelField modelField, Order order) { - this.modelField = modelField; - this.order = order; - } - - public SearchableModelField getModelField() { - return modelField; - } - - public Order getOrder() { - return order; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - OrderBy orderBy = (OrderBy) o; - return Objects.equals(modelField, orderBy.modelField) && order == orderBy.order; - } - - @Override - public int hashCode() { - return Objects.hash(modelField, order); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapCrudOperations.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapCrudOperations.java deleted file mode 100644 index d5c4ac05c98..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapCrudOperations.java +++ /dev/null @@ -1,178 +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.storage.chm; - -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.ExpirationUtils; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.storage.SearchableModelField; - -import java.util.Comparator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Stream; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; -import java.util.Objects; -import java.util.function.Predicate; - -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.utils.StreamsUtil.paginatedStream; - -/** - * - * It contains basic object CRUD operations as well as bulk {@link #read(org.keycloak.models.map.storage.QueryParameters)} - * and bulk {@link #delete(org.keycloak.models.map.storage.QueryParameters)} operations, - * and operation for determining the number of the objects satisfying given criteria - * ({@link #getCount(org.keycloak.models.map.storage.QueryParameters)}). - * - * @author hmlnarik - */ -public class ConcurrentHashMapCrudOperations implements CrudOperations { - - protected final ConcurrentMap store = new ConcurrentHashMap<>(); - - protected final Map, UpdatePredicatesFunc> fieldPredicates; - protected final StringKeyConverter keyConverter; - protected final DeepCloner cloner; - private final boolean isExpirableEntity; - - @SuppressWarnings("unchecked") - public ConcurrentHashMapCrudOperations(Class modelClass, StringKeyConverter keyConverter, DeepCloner cloner) { - this.fieldPredicates = MapFieldPredicates.getPredicates(modelClass); - this.keyConverter = keyConverter; - this.cloner = cloner; - this.isExpirableEntity = ExpirableEntity.class.isAssignableFrom(ModelEntityUtil.getEntityType(modelClass)); - } - - @Override - public V create(V value) { - K key = keyConverter.fromStringSafe(value.getId()); - if (key == null) { - key = keyConverter.yieldNewUniqueKey(); - value = cloner.from(keyConverter.keyToString(key), value); - } - store.putIfAbsent(key, value); - return value; - } - - @Override - public V read(String key) { - Objects.requireNonNull(key, "Key must be non-null"); - K k = keyConverter.fromStringSafe(key); - - V v = store.get(k); - if (v == null) return null; - return isExpirableEntity && isExpired((ExpirableEntity) v, true) ? null : v; - } - - @Override - public V update(V value) { - K key = getKeyConverter().fromStringSafe(value.getId()); - return store.replace(key, value); - } - - @Override - public boolean delete(String key) { - K k = getKeyConverter().fromStringSafe(key); - return store.remove(k) != null; - } - - @Override - public long delete(QueryParameters queryParameters) { - DefaultModelCriteria criteria = queryParameters.getModelCriteriaBuilder(); - - if (criteria == null) { - long res = store.size(); - store.clear(); - return res; - } - - @SuppressWarnings("unchecked") - MapModelCriteriaBuilder mcb = criteria.flashToModelCriteriaBuilder(createCriteriaBuilder()); - Predicate keyFilter = mcb.getKeyFilter(); - Predicate entityFilter = mcb.getEntityFilter(); - Stream> storeStream = store.entrySet().stream(); - final AtomicLong res = new AtomicLong(0); - - if (!queryParameters.getOrderBy().isEmpty()) { - Comparator comparator = MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream()); - storeStream = storeStream.sorted((entry1, entry2) -> comparator.compare(entry1.getValue(), entry2.getValue())); - } - - paginatedStream(storeStream.filter(next -> keyFilter.test(next.getKey()) && entityFilter.test(next.getValue())) - , queryParameters.getOffset(), queryParameters.getLimit()) - .peek(item -> {res.incrementAndGet();}) - .map(Entry::getKey) - .forEach(store::remove); - - return res.get(); - } - - public MapModelCriteriaBuilder createCriteriaBuilder() { - return new MapModelCriteriaBuilder<>(keyConverter, fieldPredicates); - } - - public StringKeyConverter getKeyConverter() { - return keyConverter; - } - - @Override - public Stream read(QueryParameters queryParameters) { - DefaultModelCriteria criteria = queryParameters.getModelCriteriaBuilder(); - - if (criteria == null) { - return Stream.empty(); - } - - MapModelCriteriaBuilder mcb = criteria.flashToModelCriteriaBuilder(createCriteriaBuilder()); - Stream> stream = store.entrySet().stream(); - - Predicate keyFilter = mcb.getKeyFilter(); - Predicate entityFilter; - - if (isExpirableEntity) { - entityFilter = mcb.getEntityFilter().and(ExpirationUtils::isNotExpired); - } else { - entityFilter = mcb.getEntityFilter(); - } - - Stream valueStream = stream.filter(me -> keyFilter.test(me.getKey()) && entityFilter.test(me.getValue())) - .map(Map.Entry::getValue); - - if (!queryParameters.getOrderBy().isEmpty()) { - valueStream = valueStream.sorted(MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream())); - } - - return paginatedStream(valueStream, queryParameters.getOffset(), queryParameters.getLimit()); - } - - @Override - public long getCount(QueryParameters queryParameters) { - return read(queryParameters).count(); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java deleted file mode 100644 index 82d0fb2a762..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java +++ /dev/null @@ -1,603 +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.storage.chm; - -import org.keycloak.models.KeycloakTransaction; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.common.UpdatableEntity; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.jboss.logging.Logger; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.storage.SearchableModelField; -import java.util.function.Consumer; -import java.util.Collection; -import java.util.Set; -import java.util.function.BiFunction; - -public class ConcurrentHashMapStorage> implements MapStorage, KeycloakTransaction, HasRealmId { - - private final static Logger log = Logger.getLogger(ConcurrentHashMapStorage.class); - - protected boolean active; - protected boolean rollback; - protected final TaskMap tasks = new TaskMap(); - protected final CRUD map; - protected final StringKeyConverter keyConverter; - protected final DeepCloner cloner; - protected final Map, UpdatePredicatesFunc> fieldPredicates; - protected final EntityField realmIdEntityField; - private String realmId; - private final boolean mapHasRealmId; - - protected static final class TaskKey { - private final String realmId; - private final String key; - - public TaskKey(String realmId, String key) { - this.realmId = realmId; - this.key = key; - } - - private Object getRealmId() { - return this.realmId; - } - - public String getKey() { - return key; - } - - @Override - public int hashCode() { - return Objects.hash(this.key, this.realmId); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TaskKey other = (TaskKey) obj; - return Objects.equals(this.key, other.key) && Objects.equals(this.realmId, other.realmId); - } - - @Override - public String toString() { - return key + " / " + realmId; - } - - static TaskKey keyFor(String realmId, String id) { - return new TaskKey(realmId, id); - } - } - - protected class TaskMap { - - private final Map map = new LinkedHashMap<>(); - - public boolean isEmpty() { - return map.isEmpty(); - } - - public boolean containsKey(String key) { - return map.containsKey(TaskKey.keyFor(realmId, key)); - } - - public MapTaskWithValue get(String key) { - return map.get(TaskKey.keyFor(realmId, key)); - } - - public MapTaskWithValue put(String key, MapTaskWithValue value) { - return map.put(TaskKey.keyFor(realmId, key), value); - } - - public void clear() { - map.clear(); - } - - public Collection values() { - return map.values(); - } - - public Set> entrySet() { - return map.entrySet(); - } - - public MapTaskWithValue merge(String key, MapTaskWithValue value, BiFunction remappingFunction) { - return map.merge(TaskKey.keyFor(realmId, key), value, remappingFunction); - } - } - - protected enum MapOperation { - CREATE, UPDATE, DELETE, - } - - public ConcurrentHashMapStorage(CRUD map, StringKeyConverter keyConverter, DeepCloner cloner, Map, UpdatePredicatesFunc> fieldPredicates) { - this(map, keyConverter, cloner, fieldPredicates, null); - } - - public ConcurrentHashMapStorage(CRUD map, StringKeyConverter keyConverter, DeepCloner cloner, Map, UpdatePredicatesFunc> fieldPredicates, EntityField realmIdEntityField) { - this.map = map; - this.keyConverter = keyConverter; - this.cloner = cloner; - this.fieldPredicates = fieldPredicates; - this.realmIdEntityField = realmIdEntityField; - this.mapHasRealmId = map instanceof HasRealmId; - } - - @Override - public void begin() { - active = true; - } - - @Override - public void commit() { - if (rollback) { - throw new RuntimeException("Rollback only!"); - } - - final Consumer setRealmId = mapHasRealmId ? ((HasRealmId) map)::setRealmId : a -> {}; - if (! tasks.isEmpty()) { - log.tracef("Commit - %s", map); - for (MapTaskWithValue value : tasks.values()) { - setRealmId.accept(value.getRealmId()); - value.execute(); - } - } - } - - @Override - public void rollback() { - tasks.clear(); - } - - @Override - public void setRollbackOnly() { - rollback = true; - } - - @Override - public boolean getRollbackOnly() { - return rollback; - } - - @Override - public boolean isActive() { - return active; - } - - private MapModelCriteriaBuilder createCriteriaBuilder() { - return new MapModelCriteriaBuilder<>(keyConverter, fieldPredicates); - } - - /** - * Adds a given task if not exists for the given key - */ - protected void addTask(String key, MapTaskWithValue task) { - log.tracef("Adding operation %s for %s @ %08x", task.getOperation(), key, System.identityHashCode(task.getValue())); - - tasks.merge(key, task, MapTaskCompose::new); - } - - /** - * Returns a deep clone of an entity. If the clone is already in the transaction, returns this one. - *

- * Usually used before giving an entity from a source back to the caller, - * to prevent changing it directly in the data store, but to keep transactional properties. - * @param origEntity Original entity - * @return - */ - public V registerEntityForChanges(V origEntity) { - final String key = origEntity.getId(); - // If the entity is listed in the transaction already, return it directly - if (tasks.containsKey(key)) { - MapTaskWithValue current = tasks.get(key); - return current.getValue(); - } - // Else enlist its copy in the transaction. Never return direct reference to the underlying map - final V res = cloner.from(origEntity); - return updateIfChanged(res, e -> e.isUpdated()); - } - - @Override - public V read(String sKey) { - try { - // TODO: Consider using Optional rather than handling NPE - final V entity = read(sKey, map::read); - if (entity == null) { - log.debugf("Could not read object for key %s", sKey); - return null; - } - return postProcess(registerEntityForChanges(entity)); - } catch (NullPointerException ex) { - return null; - } - } - - private V read(String key, Function defaultValueFunc) { - MapTaskWithValue current = tasks.get(key); - // If the key exists, then it has entered the "tasks" after bulk delete that could have - // removed it, so looking through bulk deletes is irrelevant - if (tasks.containsKey(key)) { - return current.getValue(); - } - - // If the key does not exist, then it would be read fresh from the storage, but then it - // could have been removed by some bulk delete in the existing tasks. Check it. - final V value = defaultValueFunc.apply(key); - for (MapTaskWithValue val : tasks.values()) { - if (val instanceof ConcurrentHashMapStorage.BulkDeleteOperation) { - final BulkDeleteOperation delOp = (BulkDeleteOperation) val; - if (! delOp.getFilterForNonDeletedObjects().test(value)) { - return null; - } - } - } - - return value; - } - - /** - * Returns the stream of records that match given criteria and includes changes made in this transaction, i.e. - * the result contains updates and excludes records that have been deleted in this transaction. - * - * @param queryParameters - * @return - */ - @Override - public Stream read(QueryParameters queryParameters) { - DefaultModelCriteria mcb = queryParameters.getModelCriteriaBuilder(); - MapModelCriteriaBuilder mapMcb = mcb.flashToModelCriteriaBuilder(createCriteriaBuilder()); - - Predicate filterOutAllBulkDeletedObjects = tasks.values().stream() - .filter(BulkDeleteOperation.class::isInstance) - .map(BulkDeleteOperation.class::cast) - .map(BulkDeleteOperation::getFilterForNonDeletedObjects) - .reduce(Predicate::and) - .orElse(v -> true); - - Stream updatedAndNotRemovedObjectsStream = this.map.read(queryParameters) - .filter(filterOutAllBulkDeletedObjects) - .map(this::getUpdated) // If the object has been removed, store.get will return null, otherwise it will return me.getValue() - .filter(Objects::nonNull) - .map(this::registerEntityForChanges); - - updatedAndNotRemovedObjectsStream = postProcess(updatedAndNotRemovedObjectsStream); - - if (mapMcb != null) { - // Add explicit filtering for the case when the map returns raw stream of untested values (ie. realize sequential scan) - updatedAndNotRemovedObjectsStream = updatedAndNotRemovedObjectsStream - .filter(e -> mapMcb.getKeyFilter().test(keyConverter.fromStringSafe(e.getId()))) - .filter(mapMcb.getEntityFilter()); - } - - // In case of created values stored in MapKeycloakTransaction, we need filter those according to the filter - Stream res = mapMcb == null - ? updatedAndNotRemovedObjectsStream - : Stream.concat( - createdValuesStream(mapMcb.getKeyFilter(), mapMcb.getEntityFilter()), - updatedAndNotRemovedObjectsStream - ); - - if (!queryParameters.getOrderBy().isEmpty()) { - res = res.sorted(MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream())); - } - - - return res; - } - - @Override - public long getCount(QueryParameters queryParameters) { - return read(queryParameters).count(); - } - - private V getUpdated(V orig) { - MapTaskWithValue current = orig == null ? null : tasks.get(orig.getId()); - return current == null ? orig : current.getValue(); - } - - @Override - public V create(V value) { - String key = map.determineKeyFromValue(value); - if (key == null) { - K newKey = keyConverter.yieldNewUniqueKey(); - key = keyConverter.keyToString(newKey); - value = cloner.from(key, value); - } else if (! key.equals(value.getId())) { - value = cloner.from(key, value); - } else { - value = cloner.from(value); - } - addTask(key, new CreateOperation(value)); - return postProcess(value); - } - - public V updateIfChanged(V value, Predicate shouldPut) { - String key = value.getId(); - log.tracef("Adding operation UPDATE_IF_CHANGED for %s @ %08x", key, System.identityHashCode(value)); - - String taskKey = key; - MapTaskWithValue op = new MapTaskWithValue(value) { - @Override - public void execute() { - if (shouldPut.test(getValue())) { - map.update(getValue()); - } - } - @Override public MapOperation getOperation() { return MapOperation.UPDATE; } - }; - return tasks.merge(taskKey, op, this::merge).getValue(); - } - - @Override - public boolean delete(String key) { - tasks.merge(key, new DeleteOperation(key), this::merge); - return true; - } - - @Override - public long delete(QueryParameters queryParameters) { - log.tracef("Adding operation DELETE_BULK"); - - K artificialKey = keyConverter.yieldNewUniqueKey(); - - // Remove all tasks that create / update / delete objects deleted by the bulk removal. - final BulkDeleteOperation bdo = new BulkDeleteOperation(queryParameters); - Predicate filterForNonDeletedObjects = bdo.getFilterForNonDeletedObjects(); - long res = 0; - for (Iterator> it = tasks.entrySet().iterator(); it.hasNext();) { - Entry me = it.next(); - if (! filterForNonDeletedObjects.test(me.getValue().getValue())) { - log.tracef(" [DELETE_BULK] removing %s", me.getKey()); - it.remove(); - res++; - } - } - - tasks.put(keyConverter.keyToString(artificialKey), bdo); - - return res + bdo.getCount(); - } - - @Override - public boolean exists(String key) { - if (tasks.containsKey(key)) { - MapTaskWithValue o = tasks.get(key); - return o.getValue() != null; - } - - // Check if there is a bulk delete operation in which case read the full entity - for (MapTaskWithValue val : tasks.values()) { - if (val instanceof ConcurrentHashMapStorage.BulkDeleteOperation) { - return read(key) != null; - } - } - - return map.exists(key); - } - - private Stream createdValuesStream(Predicate keyFilter, Predicate entityFilter) { - return this.tasks.entrySet().stream() - .filter(me -> Objects.equals(realmId, me.getKey().getRealmId()) && keyFilter.test(keyConverter.fromStringSafe(me.getKey().getKey()))) - .map(Map.Entry::getValue) - .filter(v -> v.containsCreate() && ! v.isReplace()) - .map(MapTaskWithValue::getValue) - .filter(Objects::nonNull) - .filter(entityFilter) - // make a snapshot - .collect(Collectors.toList()).stream(); - } - - private MapTaskWithValue merge(MapTaskWithValue oldValue, MapTaskWithValue newValue) { - switch (newValue.getOperation()) { - case DELETE: - return newValue; - default: - return new MapTaskCompose(oldValue, newValue); - } - } - - protected abstract class MapTaskWithValue { - protected final V value; - private final String realmId; - - public MapTaskWithValue(V value) { - this.value = value; - this.realmId = ConcurrentHashMapStorage.this.realmId; - } - - public V getValue() { - return value; - } - - public boolean containsCreate() { - return MapOperation.CREATE == getOperation(); - } - - public boolean containsRemove() { - return MapOperation.DELETE == getOperation(); - } - - public boolean isReplace() { - return false; - } - - public String getRealmId() { - return realmId; - } - - public abstract MapOperation getOperation(); - public abstract void execute(); - } - - private class MapTaskCompose extends MapTaskWithValue { - - private final MapTaskWithValue oldValue; - private final MapTaskWithValue newValue; - - public MapTaskCompose(MapTaskWithValue oldValue, MapTaskWithValue newValue) { - super(null); - this.oldValue = oldValue; - this.newValue = newValue; - } - - @Override - public void execute() { - oldValue.execute(); - newValue.execute(); - } - - @Override - public V getValue() { - return newValue.getValue(); - } - - @Override - public MapOperation getOperation() { - return null; - } - - @Override - public boolean containsCreate() { - return oldValue.containsCreate() || newValue.containsCreate(); - } - - @Override - public boolean containsRemove() { - return oldValue.containsRemove() || newValue.containsRemove(); - } - - @Override - public boolean isReplace() { - return (newValue.getOperation() == MapOperation.CREATE && oldValue.containsRemove()) || - (oldValue instanceof ConcurrentHashMapStorage.MapTaskCompose && ((MapTaskCompose) oldValue).isReplace()); - } - } - - private class CreateOperation extends MapTaskWithValue { - public CreateOperation(V value) { - super(value); - } - - @Override public void execute() { map.create(getValue()); } - @Override public MapOperation getOperation() { return MapOperation.CREATE; } - } - - private class DeleteOperation extends MapTaskWithValue { - private final String key; - - public DeleteOperation(String key) { - super(null); - this.key = key; - } - - @Override public void execute() { map.delete(key); } - @Override public MapOperation getOperation() { return MapOperation.DELETE; } - } - - private class BulkDeleteOperation extends MapTaskWithValue { - - private final QueryParameters queryParameters; - - public BulkDeleteOperation(QueryParameters queryParameters) { - super(null); - this.queryParameters = queryParameters; - } - - @Override - @SuppressWarnings("unchecked") - public void execute() { - map.delete(queryParameters); - } - - public Predicate getFilterForNonDeletedObjects() { - DefaultModelCriteria mcb = queryParameters.getModelCriteriaBuilder(); - MapModelCriteriaBuilder mmcb = mcb.flashToModelCriteriaBuilder(createCriteriaBuilder()); - - Predicate entityFilter = mmcb.getEntityFilter(); - Predicate keyFilter = mmcb.getKeyFilter(); - return v -> v == null || ! (keyFilter.test(keyConverter.fromStringSafe(v.getId())) && entityFilter.test(v)); - } - - @Override - public MapOperation getOperation() { - return MapOperation.DELETE; - } - - private long getCount() { - return map.getCount(queryParameters); - } - } - - @Override - public String getRealmId() { - if (mapHasRealmId) { - return ((HasRealmId) map).getRealmId(); - } - return null; - } - - @Override - @SuppressWarnings("unchecked") - public void setRealmId(String realmId) { - if (mapHasRealmId) { - ((HasRealmId) map).setRealmId(realmId); - this.realmId = realmId; - } else { - this.realmId = null; - } - } - - private V postProcess(V value) { - return (realmId == null || value == null) - ? value - : ModelEntityUtil.supplyReadOnlyFieldValueIfUnset(value, realmIdEntityField, realmId); - } - - private Stream postProcess(Stream stream) { - if (this.realmId == null) { - return stream; - } - - String localRealmId = this.realmId; - return stream.map((V value) -> ModelEntityUtil.supplyReadOnlyFieldValueIfUnset(value, realmIdEntityField, localRealmId)); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java deleted file mode 100644 index 76ef8a2a19d..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java +++ /dev/null @@ -1,67 +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.storage.chm; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag; - -import static org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory.CLONER; - -/** - * - * @author hmlnarik - */ -public class ConcurrentHashMapStorageProvider implements MapStorageProvider { - - private final KeycloakSession session; - private final ConcurrentHashMapStorageProviderFactory factory; - private final int factoryId; - - public ConcurrentHashMapStorageProvider(KeycloakSession session, ConcurrentHashMapStorageProviderFactory factory, int factoryId) { - this.session = session; - this.factory = factory; - this.factoryId = factoryId; - } - - @Override - public void close() { - } - - @Override - @SuppressWarnings("unchecked") - public MapStorage getMapStorage(Class modelType, Flag... flags) { - return SessionAttributesUtils.createMapStorageIfAbsent(session, getClass(), modelType, factoryId, () -> { - ConcurrentHashMapStorage store = getMapStorage(modelType, factory.getStorage(modelType, flags)); - session.getTransactionManager().enlist(store); - return store; - }); - } - - private ConcurrentHashMapStorage getMapStorage(Class modelType, ConcurrentHashMapCrudOperations crud) { - if (modelType == SingleUseObjectValueModel.class) { - return new SingleUseObjectMapStorage(crud, factory.getKeyConverter(modelType), CLONER, MapFieldPredicates.getPredicates(modelType)); - } - return new ConcurrentHashMapStorage(crud, factory.getKeyConverter(modelType), CLONER, MapFieldPredicates.getPredicates(modelType)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java deleted file mode 100644 index 5315031b9d1..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java +++ /dev/null @@ -1,327 +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.storage.chm; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.map.common.SessionAttributesUtils; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapAuthenticationSessionEntityImpl; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityImpl; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntityImpl; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntityImpl; -import org.keycloak.models.map.authorization.entity.MapResourceEntityImpl; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntityImpl; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntityImpl; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.component.AmphibianProviderFactory; -import org.keycloak.Config.Scope; -import org.keycloak.common.Profile; -import org.keycloak.component.ComponentModelScope; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.map.client.MapClientEntityImpl; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; -import org.keycloak.models.map.clientscope.MapClientScopeEntityImpl; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.Serialization; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.events.MapAdminEventEntityImpl; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.events.MapAuthEventEntityImpl; -import org.keycloak.models.map.group.MapGroupEntityImpl; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityImpl; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.realm.MapRealmEntityImpl; -import org.keycloak.models.map.realm.entity.*; -import org.keycloak.models.map.role.MapRoleEntityImpl; -import com.fasterxml.jackson.databind.JavaType; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.EnumSet; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import org.jboss.logging.Logger; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntityImpl; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.user.MapUserConsentEntityImpl; -import org.keycloak.models.map.user.MapUserCredentialEntityImpl; -import org.keycloak.models.map.user.MapUserEntityImpl; -import org.keycloak.models.map.user.MapUserFederatedIdentityEntityImpl; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; -import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityImpl; -import org.keycloak.models.map.userSession.MapUserSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntityImpl; -import org.keycloak.provider.EnvironmentDependentProviderFactory; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; - -import static org.keycloak.models.map.common.SessionAttributesUtils.grabNewFactoryIdentifier; -import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName; -import static org.keycloak.models.map.storage.ModelEntityUtil.getModelNames; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -/** - * - * @author hmlnarik - */ -public class ConcurrentHashMapStorageProviderFactory implements AmphibianProviderFactory, MapStorageProviderFactory, EnvironmentDependentProviderFactory { - - public static final String PROVIDER_ID = "concurrenthashmap"; - - private static final Logger LOG = Logger.getLogger(ConcurrentHashMapStorageProviderFactory.class); - - private final ConcurrentHashMap> storages = new ConcurrentHashMap<>(); - - private final Map keyConverters = new HashMap<>(); - - private File storageDirectory; - - private String suffix; - - private StringKeyConverter defaultKeyConverter; - - private final int factoryId = grabNewFactoryIdentifier(); - - protected final static DeepCloner CLONER = new DeepCloner.Builder() - .genericCloner(Serialization::from) - .constructor(MapClientEntityImpl.class, MapClientEntityImpl::new) - .constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new) - .constructor(MapGroupEntityImpl.class, MapGroupEntityImpl::new) - .constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new) - .constructor(MapUserEntityImpl.class, MapUserEntityImpl::new) - .constructor(MapUserCredentialEntityImpl.class, MapUserCredentialEntityImpl::new) - .constructor(MapUserFederatedIdentityEntityImpl.class, MapUserFederatedIdentityEntityImpl::new) - .constructor(MapUserConsentEntityImpl.class, MapUserConsentEntityImpl::new) - .constructor(MapClientScopeEntityImpl.class, MapClientScopeEntityImpl::new) - .constructor(MapResourceServerEntityImpl.class, MapResourceServerEntityImpl::new) - .constructor(MapResourceEntityImpl.class, MapResourceEntityImpl::new) - .constructor(MapScopeEntity.class, MapScopeEntityImpl::new) - .constructor(MapPolicyEntity.class, MapPolicyEntityImpl::new) - .constructor(MapPermissionTicketEntity.class, MapPermissionTicketEntityImpl::new) - .constructor(MapRealmEntity.class, MapRealmEntityImpl::new) - .constructor(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl::new) - .constructor(MapAuthenticationFlowEntity.class, MapAuthenticationFlowEntityImpl::new) - .constructor(MapAuthenticatorConfigEntity.class, MapAuthenticatorConfigEntityImpl::new) - .constructor(MapClientInitialAccessEntity.class, MapClientInitialAccessEntityImpl::new) - .constructor(MapComponentEntity.class, MapComponentEntityImpl::new) - .constructor(MapIdentityProviderEntity.class, MapIdentityProviderEntityImpl::new) - .constructor(MapIdentityProviderMapperEntity.class, MapIdentityProviderMapperEntityImpl::new) - .constructor(MapOTPPolicyEntity.class, MapOTPPolicyEntityImpl::new) - .constructor(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl::new) - .constructor(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl::new) - .constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new) - .constructor(MapRootAuthenticationSessionEntity.class, MapRootAuthenticationSessionEntityImpl::new) - .constructor(MapAuthenticationSessionEntity.class, MapAuthenticationSessionEntityImpl::new) - .constructor(MapUserLoginFailureEntity.class, MapUserLoginFailureEntityImpl::new) - .constructor(MapUserSessionEntity.class, MapUserSessionEntityImpl::new) - .constructor(MapAuthenticatedClientSessionEntity.class, MapAuthenticatedClientSessionEntityImpl::new) - .constructor(MapAuthEventEntity.class, MapAuthEventEntityImpl::new) - .constructor(MapAdminEventEntity.class, MapAdminEventEntityImpl::new) - .constructor(MapSingleUseObjectEntity.class, MapSingleUseObjectEntityImpl::new) - .build(); - - private static final Map KEY_CONVERTERS = new HashMap<>(); - static { - KEY_CONVERTERS.put("uuid", StringKeyConverter.UUIDKey.INSTANCE); - KEY_CONVERTERS.put("string", StringKeyConverter.StringKey.INSTANCE); - KEY_CONVERTERS.put("ulong", StringKeyConverter.ULongKey.INSTANCE); - } - - @Override - public MapStorageProvider create(KeycloakSession session) { - return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, ConcurrentHashMapStorageProvider.class, session1 -> new ConcurrentHashMapStorageProvider(session, this, factoryId)); - } - - - @Override - public void init(Scope config) { - if (config instanceof ComponentModelScope) { - this.suffix = "-" + ((ComponentModelScope) config).getComponentId(); - } else { - this.suffix = ""; - } - - final String keyType = config.get("keyType", "uuid"); - defaultKeyConverter = getKeyConverter(keyType); - for (String name : getModelNames()) { - keyConverters.put(name, getKeyConverter(config.get("keyType." + name, keyType))); - } - - final String dir = config.get("dir"); - try { - if (dir == null || dir.trim().isEmpty()) { - LOG.warn("No directory set, created objects will not survive server restart"); - this.storageDirectory = null; - } else { - File f = new File(dir); - Files.createDirectories(f.toPath()); - if (f.exists()) { - this.storageDirectory = f; - } else { - LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir); - this.storageDirectory = null; - } - } - } catch (IOException ex) { - LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir); - this.storageDirectory = null; - } - } - - private StringKeyConverter getKeyConverter(final String keyType) throws IllegalArgumentException { - StringKeyConverter res = KEY_CONVERTERS.get(keyType); - if (res == null) { - throw new IllegalArgumentException("Unknown key type: " + keyType); - } - return res; - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - storages.forEach(this::storeMap); - } - - @SuppressWarnings("unchecked") - private void storeMap(String mapName, CrudOperations store) { - if (mapName != null) { - File f = getFile(mapName); - try { - if (storageDirectory != null) { - LOG.debugf("Storing contents to %s", f.getCanonicalPath()); - final DefaultModelCriteria readAllCriteria = criteria(); - Serialization.MAPPER.writeValue(f, store.read(withCriteria(readAllCriteria))); - } else { - LOG.debugf("Not storing contents of %s because directory not set", mapName); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } - - @SuppressWarnings("unchecked") - private ConcurrentHashMapCrudOperations loadMap(String mapName, - Class modelType, - EnumSet flags) { - final StringKeyConverter kc = keyConverters.getOrDefault(mapName, defaultKeyConverter); - Class valueType = ModelEntityUtil.getEntityType(modelType); - LOG.debugf("Initializing new map storage: %s", mapName); - - ConcurrentHashMapCrudOperations store; - if(modelType == SingleUseObjectValueModel.class) { - store = new SingleUseObjectConcurrentHashMapCrudOperations(kc, CLONER) { - @Override - public String toString() { - return "ConcurrentHashMapStorage(" + mapName + suffix + ")"; - } - }; - } else { - store = new ConcurrentHashMapCrudOperations<>(modelType, kc, CLONER) { - @Override - public String toString() { - return "ConcurrentHashMapStorage(" + mapName + suffix + ")"; - } - }; - } - - if (! flags.contains(Flag.INITIALIZE_EMPTY)) { - final File f = getFile(mapName); - if (f != null && f.exists()) { - try { - LOG.debugf("Restoring contents from %s", f.getCanonicalPath()); - Class valueImplType = CLONER.newInstanceType(valueType); - if (valueImplType == null) { - valueImplType = valueType; - } - JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(LinkedList.class, valueImplType); - - List values = Serialization.MAPPER.readValue(f, type); - values.forEach((V mce) -> store.create(mce)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } - - return store; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @SuppressWarnings("unchecked") - public ConcurrentHashMapCrudOperations getStorage( - Class modelType, Flag... flags) { - EnumSet f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags); - String name = getModelName(modelType, modelType.getSimpleName()); - /* From ConcurrentHashMapStorage.computeIfAbsent javadoc: - * - * "... the computation [...] must not attempt to update any other mappings of this map." - */ - - return (ConcurrentHashMapCrudOperations) storages.computeIfAbsent(name, n -> loadMap(name, modelType, f)); - } - - public StringKeyConverter getKeyConverter(Class modelType) { - return keyConverters.getOrDefault(getModelName(modelType, modelType.getSimpleName()), defaultKeyConverter); - } - - private File getFile(String fileName) { - return storageDirectory == null - ? null - : new File(storageDirectory, "map-" + fileName + suffix + ".json"); - } - - @Override - public String getHelpText() { - return "In-memory ConcurrentHashMap storage"; - } - - @Override - public List getConfigProperties() { - return Collections.emptyList(); - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/CriteriaOperator.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/CriteriaOperator.java deleted file mode 100644 index 95e7465a6b0..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/CriteriaOperator.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * 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.storage.chm; - -import org.jboss.logging.Logger; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -class CriteriaOperator { - - private static final EnumMap>> OPERATORS = new EnumMap<>(Operator.class); - - private static final Logger LOG = Logger.getLogger(CriteriaOperator.class.getSimpleName()); - - private static final Predicate ALWAYS_FALSE = o -> false; - private static final Predicate ALWAYS_TRUE = o -> true; - private static final Pattern LIKE_PATTERN_DELIMITER = Pattern.compile("%+"); - - static { - OPERATORS.put(Operator.EQ, CriteriaOperator::eq); - OPERATORS.put(Operator.NE, CriteriaOperator::ne); - OPERATORS.put(Operator.EXISTS, CriteriaOperator::exists); - OPERATORS.put(Operator.NOT_EXISTS, CriteriaOperator::notExists); - OPERATORS.put(Operator.LT, CriteriaOperator::lt); - OPERATORS.put(Operator.LE, CriteriaOperator::le); - OPERATORS.put(Operator.GT, CriteriaOperator::gt); - OPERATORS.put(Operator.GE, CriteriaOperator::ge); - OPERATORS.put(Operator.IN, CriteriaOperator::in); - OPERATORS.put(Operator.LIKE, CriteriaOperator::like); - OPERATORS.put(Operator.ILIKE, CriteriaOperator::ilike); - - // Check that all operators are covered - EnumSet s = EnumSet.allOf(Operator.class); - s.removeAll(OPERATORS.keySet()); - if (! s.isEmpty()) { - throw new IllegalStateException("Some operators are not implemented: " + s); - } - } - - /** - * Returns a predicate {@code P(x)} for comparing {@code value} and {@code x} as {@code x OP value}. - * Implementation note: Note that this may mean reverse logic to e.g. {@link Comparable#compareTo}. - * @param op - * @param value - * @return - */ - public static Predicate predicateFor(Operator op, Object[] value) { - final Function> funcToGetPredicate = OPERATORS.get(op); - if (funcToGetPredicate == null) { - throw new IllegalArgumentException("Unknown operator: " + op); - } - return funcToGetPredicate.apply(value); - } - - private static Object getFirstArrayElement(Object[] value) throws IllegalStateException { - if (value == null || value.length != 1) { - throw new IllegalStateException("Invalid argument: " + Arrays.toString(value)); - } - return value[0]; - } - - public static Predicate eq(Object[] value) { - Object value0 = getFirstArrayElement(value); - return new Predicate() { - @Override public boolean test(Object v) { return Objects.equals(v, value0); } - }; - } - - public static Predicate ne(Object[] value) { - Object value0 = getFirstArrayElement(value); - return new Predicate() { - @Override public boolean test(Object v) { return ! Objects.equals(v, value0); } - }; - } - - public static Predicate exists(Object[] value) { - if (value != null && value.length != 0) { - throw new IllegalStateException("Invalid argument: " + Arrays.toString(value)); - } - - return CriteriaOperator::collectionAwareExists; - } - - private static boolean collectionAwareExists(Object checkedObject) { - if (checkedObject instanceof Collection) { - return !((Collection) checkedObject).isEmpty(); - } - - return Objects.nonNull(checkedObject); - } - - public static Predicate notExists(Object[] value) { - if (value != null && value.length != 0) { - throw new IllegalStateException("Invalid argument: " + Arrays.toString(value)); - } - - return CriteriaOperator::collectionAwareNotExists; - } - - private static boolean collectionAwareNotExists(Object checkedObject) { - if (Objects.isNull(checkedObject)) return true; - - if (checkedObject instanceof Collection) { - return ((Collection) checkedObject).isEmpty(); - } - - return false; - } - - public static Predicate in(Object[] value) { - if (value == null || value.length == 0) { - return ALWAYS_FALSE; - } - final Collection operand; - if (value.length == 1) { - final Object value0 = value[0]; - if (value0 instanceof Collection) { - operand = (Collection) value0; - } else if (value0 instanceof Stream) { - try (Stream valueS = (Stream) value0) { - operand = valueS.collect(Collectors.toSet()); - } - } else { - operand = Collections.singleton(value0); - } - } else { - operand = new HashSet(Arrays.asList(value)); - } - return operand.isEmpty() ? ALWAYS_FALSE : new Predicate() { - @Override public boolean test(Object v) { return operand.contains(v); } - }; - } - - public static Predicate lt(Object[] value) { - return getComparisonPredicate(ComparisonPredicateImpl.Op.LT, value); - } - - public static Predicate le(Object[] value) { - return getComparisonPredicate(ComparisonPredicateImpl.Op.LE, value); - } - - public static Predicate gt(Object[] value) { - return getComparisonPredicate(ComparisonPredicateImpl.Op.GT, value); - } - - public static Predicate ge(Object[] value) { - return getComparisonPredicate(ComparisonPredicateImpl.Op.GE, value); - } - - private static Predicate getComparisonPredicate(ComparisonPredicateImpl.Op op, Object[] value) throws IllegalArgumentException { - Object value0 = getFirstArrayElement(value); - if (value0 instanceof Comparable) { - Comparable cValue = (Comparable) value0; - return new ComparisonPredicateImpl(op, cValue); - } else { - throw new IllegalArgumentException("Incomparable argument for comparison operation: " + value0); - } - } - - public static Predicate like(Object[] value) { - Object value0 = getFirstArrayElement(value); - if (value0 instanceof String) { - String sValue = (String) value0; - - if(Pattern.matches("^%+$", sValue)) { - return ALWAYS_TRUE; - } - - Pattern pValue = Pattern.compile(quoteRegex(sValue), Pattern.DOTALL); - return o -> { - return o instanceof String && pValue.matcher((String) o).matches(); - }; - } - return ALWAYS_FALSE; - } - - private static String quoteRegex(String pattern) { - return LIKE_PATTERN_DELIMITER.splitAsStream(pattern).map(Pattern::quote) - .collect(Collectors.joining(".*")) - + (pattern.endsWith("%") ? ".*" : ""); - } - - public static Predicate ilike(Object[] value) { - Object value0 = getFirstArrayElement(value); - if (value0 instanceof String) { - String sValue = (String) value0; - - if(Pattern.matches("^%+$", sValue)) { - return ALWAYS_TRUE; - } - - Pattern pValue = Pattern.compile(quoteRegex(sValue), Pattern.CASE_INSENSITIVE + Pattern.DOTALL); - return o -> { - return o instanceof String && pValue.matcher((String) o).matches(); - }; - } - return ALWAYS_FALSE; - } - - private static class ComparisonPredicateImpl implements Predicate { - - private static enum Op { - LT { @Override boolean isComparisonTrue(int compareToValue) { return compareToValue > 0; } }, - LE { @Override boolean isComparisonTrue(int compareToValue) { return compareToValue >= 0; } }, - GT { @Override boolean isComparisonTrue(int compareToValue) { return compareToValue < 0; } }, - GE { @Override boolean isComparisonTrue(int compareToValue) { return compareToValue <= 0; } }, - ; - abstract boolean isComparisonTrue(int compareToValue); - } - - private final Op op; - private final Comparable cValue; - - public ComparisonPredicateImpl(Op op, Comparable cValue) { - this.op = op; - this.cValue = cValue; - } - - @Override - public boolean test(Object o) { - try { - return o != null && op.isComparisonTrue(cValue.compareTo(o)); - } catch (ClassCastException ex) { - throw new IllegalArgumentException("Incomparable argument type for comparison operation: " + cValue.getClass().getSimpleName() + " vs. " + o.getClass().getSimpleName(), ex); - } - } - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java deleted file mode 100644 index a35c450209e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java +++ /dev/null @@ -1,603 +0,0 @@ -/* - * 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.storage.chm; - -import org.keycloak.authorization.model.PermissionTicket; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.events.Event; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserLoginFailureModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity; -import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; -import org.keycloak.models.map.authorization.entity.MapPolicyEntity; -import org.keycloak.models.map.authorization.entity.MapResourceEntity; -import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; -import org.keycloak.models.map.authorization.entity.MapScopeEntity; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.clientscope.MapClientScopeEntity; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.events.MapAdminEventEntity; -import org.keycloak.models.map.events.MapAuthEventEntity; -import org.keycloak.models.map.group.MapGroupEntity; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.role.MapRoleEntity; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.user.MapUserConsentEntity; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.SearchableModelField; - -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.user.MapUserEntity; -import org.keycloak.models.map.userSession.MapUserSessionEntity; -import org.keycloak.sessions.RootAuthenticationSessionModel; -import org.keycloak.storage.StorageId; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; -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; - -/** - * - * @author hmlnarik - */ -public class MapFieldPredicates { - - public static final Map, UpdatePredicatesFunc> CLIENT_PREDICATES = basePredicates(ClientModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> CLIENT_SCOPE_PREDICATES = basePredicates(ClientScopeModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> GROUP_PREDICATES = basePredicates(GroupModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> ROLE_PREDICATES = basePredicates(RoleModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTHENTICATION_SESSION_PREDICATES = basePredicates(RootAuthenticationSessionModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> REALM_PREDICATES = basePredicates(RealmModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTHZ_RESOURCE_SERVER_PREDICATES = basePredicates(ResourceServer.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTHZ_RESOURCE_PREDICATES = basePredicates(Resource.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTHZ_SCOPE_PREDICATES = basePredicates(Scope.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTHZ_PERMISSION_TICKET_PREDICATES = basePredicates(PermissionTicket.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTHZ_POLICY_PREDICATES = basePredicates(Policy.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> USER_LOGIN_FAILURE_PREDICATES = basePredicates(UserLoginFailureModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> USER_PREDICATES = basePredicates(UserModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> AUTH_EVENTS_PREDICATES = basePredicates(Event.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> ADMIN_EVENTS_PREDICATES = basePredicates(AdminEvent.SearchableFields.ID); - public static final Map, UpdatePredicatesFunc> ACTION_TOKEN_PREDICATES = basePredicates(SingleUseObjectValueModel.SearchableFields.ID); - - @SuppressWarnings("unchecked") - private static final Map, Map> PREDICATES = new HashMap<>(); - private static final Map, Comparator> COMPARATORS = new IdentityHashMap<>(); - - static { - put(REALM_PREDICATES, RealmModel.SearchableFields.NAME, MapRealmEntity::getName); - putIncomparable(REALM_PREDICATES, RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, MapRealmEntity::getClientInitialAccesses); - put(REALM_PREDICATES, RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, MapFieldPredicates::checkRealmsWithComponentType); - - put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, MapClientEntity::getRealmId); - put(CLIENT_PREDICATES, ClientModel.SearchableFields.CLIENT_ID, MapClientEntity::getClientId); - put(CLIENT_PREDICATES, ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, MapFieldPredicates::checkScopeMappingRole); - put(CLIENT_PREDICATES, ClientModel.SearchableFields.ENABLED, MapClientEntity::isEnabled); - put(CLIENT_PREDICATES, ClientModel.SearchableFields.ALWAYS_DISPLAY_IN_CONSOLE, MapClientEntity::isAlwaysDisplayInConsole); - put(CLIENT_PREDICATES, ClientModel.SearchableFields.ATTRIBUTE, MapFieldPredicates::checkClientAttributes); - - put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.REALM_ID, MapClientScopeEntity::getRealmId); - put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.NAME, MapClientScopeEntity::getName); - - put(GROUP_PREDICATES, GroupModel.SearchableFields.REALM_ID, MapGroupEntity::getRealmId); - put(GROUP_PREDICATES, GroupModel.SearchableFields.NAME, MapGroupEntity::getName); - put(GROUP_PREDICATES, GroupModel.SearchableFields.PARENT_ID, MapGroupEntity::getParentId); - put(GROUP_PREDICATES, GroupModel.SearchableFields.ASSIGNED_ROLE, MapFieldPredicates::checkGrantedGroupRole); - put(GROUP_PREDICATES, GroupModel.SearchableFields.ATTRIBUTE, MapFieldPredicates::checkGroupAttributes); - - put(ROLE_PREDICATES, RoleModel.SearchableFields.REALM_ID, MapRoleEntity::getRealmId); - put(ROLE_PREDICATES, RoleModel.SearchableFields.CLIENT_ID, MapRoleEntity::getClientId); - put(ROLE_PREDICATES, RoleModel.SearchableFields.DESCRIPTION, MapRoleEntity::getDescription); - put(ROLE_PREDICATES, RoleModel.SearchableFields.NAME, MapRoleEntity::getName); - put(ROLE_PREDICATES, RoleModel.SearchableFields.COMPOSITE_ROLE, MapFieldPredicates::checkCompositeRoles); - - put(USER_PREDICATES, UserModel.SearchableFields.REALM_ID, MapUserEntity::getRealmId); - put(USER_PREDICATES, UserModel.SearchableFields.USERNAME, MapUserEntity::getUsername); - put(USER_PREDICATES, UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE, MapFieldPredicates::usernameCaseInsensitive); - put(USER_PREDICATES, UserModel.SearchableFields.FIRST_NAME, MapUserEntity::getFirstName); - put(USER_PREDICATES, UserModel.SearchableFields.LAST_NAME, MapUserEntity::getLastName); - put(USER_PREDICATES, UserModel.SearchableFields.EMAIL, MapUserEntity::getEmail); - put(USER_PREDICATES, UserModel.SearchableFields.ENABLED, MapUserEntity::isEnabled); - put(USER_PREDICATES, UserModel.SearchableFields.EMAIL_VERIFIED, MapUserEntity::isEmailVerified); - put(USER_PREDICATES, UserModel.SearchableFields.FEDERATION_LINK, MapUserEntity::getFederationLink); - put(USER_PREDICATES, UserModel.SearchableFields.ATTRIBUTE, MapFieldPredicates::checkUserAttributes); - put(USER_PREDICATES, UserModel.SearchableFields.IDP_AND_USER, MapFieldPredicates::getUserIdpAliasAtIdentityProviderPredicate); - put(USER_PREDICATES, UserModel.SearchableFields.ASSIGNED_ROLE, MapFieldPredicates::checkGrantedUserRole); - put(USER_PREDICATES, UserModel.SearchableFields.ASSIGNED_GROUP, MapFieldPredicates::checkUserGroup); - put(USER_PREDICATES, UserModel.SearchableFields.CONSENT_FOR_CLIENT, MapFieldPredicates::checkUserClientConsent); - put(USER_PREDICATES, UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE, MapFieldPredicates::checkUserConsentsWithClientScope); - put(USER_PREDICATES, UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, MapFieldPredicates::getUserConsentClientFederationLink); - put(USER_PREDICATES, UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, MapUserEntity::getServiceAccountClientLink); - - put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.REALM_ID, MapRootAuthenticationSessionEntity::getRealmId); - - put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.ID, predicateForKeyField(MapResourceServerEntity::getId)); - put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.CLIENT_ID, MapResourceServerEntity::getClientId); - put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.REALM_ID, MapResourceServerEntity::getRealmId); - - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.ID, predicateForKeyField(MapResourceEntity::getId)); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.NAME, MapResourceEntity::getName); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.RESOURCE_SERVER_ID, MapResourceEntity::getResourceServerId); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.OWNER, MapResourceEntity::getOwner); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.TYPE, MapResourceEntity::getType); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.URI, MapFieldPredicates::checkResourceUri); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.SCOPE_ID, MapFieldPredicates::checkResourceScopes); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.OWNER_MANAGED_ACCESS, MapResourceEntity::isOwnerManagedAccess); - put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.REALM_ID, MapResourceEntity::getRealmId); - - put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.ID, predicateForKeyField(MapScopeEntity::getId)); - put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.RESOURCE_SERVER_ID, MapScopeEntity::getResourceServerId); - put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.NAME, MapScopeEntity::getName); - put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.REALM_ID, MapScopeEntity::getRealmId); - - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.ID, predicateForKeyField(MapPermissionTicketEntity::getId)); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.OWNER, MapPermissionTicketEntity::getOwner); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.REQUESTER, MapPermissionTicketEntity::getRequester); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.RESOURCE_SERVER_ID, MapPermissionTicketEntity::getResourceServerId); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.RESOURCE_ID, predicateForKeyField(MapPermissionTicketEntity::getResourceId)); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.SCOPE_ID, predicateForKeyField(MapPermissionTicketEntity::getScopeId)); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.POLICY_ID, predicateForKeyField(MapPermissionTicketEntity::getPolicyId)); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.GRANTED_TIMESTAMP, MapPermissionTicketEntity::getGrantedTimestamp); - put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.REALM_ID, MapPermissionTicketEntity::getRealmId); - - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ID, predicateForKeyField(MapPolicyEntity::getId)); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.NAME, MapPolicyEntity::getName); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.OWNER, MapPolicyEntity::getOwner); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.TYPE, MapPolicyEntity::getType); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.RESOURCE_SERVER_ID, MapPolicyEntity::getResourceServerId); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.REALM_ID, MapPolicyEntity::getRealmId); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.RESOURCE_ID, MapFieldPredicates::checkPolicyResources); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.SCOPE_ID, MapFieldPredicates::checkPolicyScopes); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.CONFIG, MapFieldPredicates::checkPolicyConfig); - put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ASSOCIATED_POLICY_ID, MapFieldPredicates::checkAssociatedPolicy); - - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, use -> use.getNote(CORRESPONDING_SESSION_ID)); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.REALM_ID, MapUserSessionEntity::getRealmId); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.USER_ID, MapUserSessionEntity::getUserId); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.CLIENT_ID, MapFieldPredicates::checkUserSessionContainsAuthenticatedClientSession); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_SESSION_ID, MapUserSessionEntity::getBrokerSessionId); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_USER_ID, MapUserSessionEntity::getBrokerUserId); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.IS_OFFLINE, MapUserSessionEntity::isOffline); - put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.LAST_SESSION_REFRESH, MapUserSessionEntity::getLastSessionRefresh); - - put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.REALM_ID, MapUserLoginFailureEntity::getRealmId); - put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.USER_ID, MapUserLoginFailureEntity::getUserId); - - put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.REALM_ID, MapAuthEventEntity::getRealmId); - put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.CLIENT_ID, MapAuthEventEntity::getClientId); - put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.USER_ID, MapAuthEventEntity::getUserId); - put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.TIMESTAMP, MapAuthEventEntity::getTimestamp); - put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.IP_ADDRESS, MapAuthEventEntity::getIpAddress); - put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.EVENT_TYPE, MapAuthEventEntity::getType); - - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.REALM_ID, MapAdminEventEntity::getRealmId); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.TIMESTAMP, MapAdminEventEntity::getTimestamp); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_REALM_ID, MapAdminEventEntity::getAuthRealmId); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_CLIENT_ID, MapAdminEventEntity::getAuthClientId); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_USER_ID, MapAdminEventEntity::getAuthUserId); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_IP_ADDRESS, MapAdminEventEntity::getAuthIpAddress); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.OPERATION_TYPE, MapAdminEventEntity::getOperationType); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_TYPE, MapAdminEventEntity::getResourceType); - put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_PATH, MapAdminEventEntity::getResourcePath); - - put(ACTION_TOKEN_PREDICATES, SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey); - } - - static { - PREDICATES.put(RealmModel.class, REALM_PREDICATES); - PREDICATES.put(ClientModel.class, CLIENT_PREDICATES); - PREDICATES.put(ClientScopeModel.class, CLIENT_SCOPE_PREDICATES); - PREDICATES.put(RoleModel.class, ROLE_PREDICATES); - PREDICATES.put(GroupModel.class, GROUP_PREDICATES); - PREDICATES.put(UserModel.class, USER_PREDICATES); - PREDICATES.put(RootAuthenticationSessionModel.class, AUTHENTICATION_SESSION_PREDICATES); - PREDICATES.put(ResourceServer.class, AUTHZ_RESOURCE_SERVER_PREDICATES); - PREDICATES.put(Resource.class, AUTHZ_RESOURCE_PREDICATES); - PREDICATES.put(Scope.class, AUTHZ_SCOPE_PREDICATES); - PREDICATES.put(PermissionTicket.class, AUTHZ_PERMISSION_TICKET_PREDICATES); - PREDICATES.put(Policy.class, AUTHZ_POLICY_PREDICATES); - PREDICATES.put(UserSessionModel.class, USER_SESSION_PREDICATES); - PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES); - PREDICATES.put(Event.class, AUTH_EVENTS_PREDICATES); - PREDICATES.put(AdminEvent.class, ADMIN_EVENTS_PREDICATES); - PREDICATES.put(SingleUseObjectValueModel.class, ACTION_TOKEN_PREDICATES); - } - - private static > void put( - Map, UpdatePredicatesFunc> map, - SearchableModelField field, Function extractor) { - COMPARATORS.put(field, Comparator.comparing(extractor)); - map.put(field, (mcb, op, values) -> mcb.fieldCompare(op, extractor, values)); - } - - private static void putIncomparable( - Map, UpdatePredicatesFunc> map, - SearchableModelField field, Function extractor) { - map.put(field, (mcb, op, values) -> mcb.fieldCompare(op, extractor, values)); - } - - private static void put( - Map, UpdatePredicatesFunc> map, - SearchableModelField field, UpdatePredicatesFunc function) { - map.put(field, function); - } - - private static Function predicateForKeyField(Function extractor) { - return entity -> { - Object o = extractor.apply(entity); - return o == null ? null : o.toString(); - }; - } - - private static String ensureEqSingleValue(SearchableModelField field, String parameterName, Operator op, Object[] values) throws CriterionNotSupportedException { - return ensureEqSingleValue(field, parameterName, op, values, String.class); - } - - private static T ensureEqSingleValue(SearchableModelField field, String parameterName, Operator op, Object[] values, Class expectedType) throws CriterionNotSupportedException { - if (op != Operator.EQ) { - throw new CriterionNotSupportedException(field, op); - } - if (values == null || values.length != 1) { - throw new CriterionNotSupportedException(field, op, "Invalid arguments, expected (" + parameterName + "), got: " + Arrays.toString(values)); - } - - final Object ob = values[0]; - if (!expectedType.isAssignableFrom(ob.getClass())) { - throw new CriterionNotSupportedException(field, op, "Invalid arguments, expected (" + expectedType.getName() + "), got: " + Arrays.toString(values)); - } - - return expectedType.cast(ob); - } - - private static MapModelCriteriaBuilder checkScopeMappingRole(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String roleIdS = ensureEqSingleValue(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "role_id", op, values); - Function getter; - 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 -> Optional.ofNullable(ge.getGrantedRoles()).orElse(Collections.emptySet()).contains(roleIdS); - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder usernameCaseInsensitive(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String) { - values[i] = KeycloakModelUtils.toLowerCaseSafe((String) values[i]); - } - } - - Predicate valueComparator = CriteriaOperator.predicateFor(op, values); - Function getter = ue -> valueComparator.test(KeycloakModelUtils.toLowerCaseSafe(ue.getUsername())); - return mcb.fieldCompare(Boolean.TRUE::equals, getter); -} - - private static MapModelCriteriaBuilder getUserConsentClientFederationLink(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String providerId = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, "provider_id", op, values); - String providerIdS = new StorageId((String) providerId, "").getId(); - Function getter; - getter = ue -> Optional.ofNullable(ue.getUserConsents()).orElseGet(Collections::emptySet).stream().map(MapUserConsentEntity::getClientId).anyMatch(v -> v != null && v.startsWith(providerIdS)); - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkUserAttributes(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - if (values == null || values.length <= 1) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (attribute_name, ...), got: " + Arrays.toString(values)); - } - - final Object attrName = values[0]; - if (! (attrName instanceof String)) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values)); - } - String attrNameS = (String) attrName; - Function getter; - Object[] realValues = new Object[values.length - 1]; - System.arraycopy(values, 1, realValues, 0, values.length - 1); - Predicate valueComparator = CriteriaOperator.predicateFor(op, realValues); - getter = ue -> { - final List attrs = ue.getAttribute(attrNameS); - return attrs != null && attrs.stream().anyMatch(valueComparator); - }; - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkClientAttributes(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - if (values == null || values.length != 2) { - throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected attribute_name-value pair, got: " + Arrays.toString(values)); - } - - final Object attrName = values[0]; - if (! (attrName instanceof String)) { - throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values)); - } - String attrNameS = (String) attrName; - Object[] realValues = new Object[values.length - 1]; - System.arraycopy(values, 1, realValues, 0, values.length - 1); - Predicate valueComparator = CriteriaOperator.predicateFor(op, realValues); - Function getter = ue -> { - final List attrs = ue.getAttribute(attrNameS); - return attrs != null && attrs.stream().anyMatch(valueComparator); - }; - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkGroupAttributes(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - if (values == null || values.length != 2) { - throw new CriterionNotSupportedException(GroupModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected attribute_name-value pair, got: " + Arrays.toString(values)); - } - - final Object attrName = values[0]; - if (! (attrName instanceof String)) { - throw new CriterionNotSupportedException(GroupModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values)); - } - String attrNameS = (String) attrName; - Object[] realValues = new Object[values.length - 1]; - System.arraycopy(values, 1, realValues, 0, values.length - 1); - Predicate valueComparator = CriteriaOperator.predicateFor(op, realValues); - Function getter = ue -> { - final List attrs = ue.getAttribute(attrNameS); - return attrs != null && attrs.stream().anyMatch(valueComparator); - }; - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkCompositeRoles(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String roleIdS = ensureEqSingleValue(RoleModel.SearchableFields.COMPOSITE_ROLE, "composite_role_id", op, values); - Function getter; - getter = re -> Optional.ofNullable(re.getCompositeRoles()).orElseGet(Collections::emptySet).contains(roleIdS); - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkGrantedUserRole(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String roleIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values); - Function getter; - getter = ue -> Optional.ofNullable(ue.getRolesMembership()).orElseGet(Collections::emptySet).contains(roleIdS); - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkResourceUri(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - - if (Operator.EXISTS.equals(op)) { - getter = re -> re.getUris() != null && !re.getUris().isEmpty(); - } else if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { - Collection c = (Collection) values[0]; - getter = re -> Optional.ofNullable(re.getUris()).orElseGet(Collections::emptySet).stream().anyMatch(c::contains); - } else { - String uri = ensureEqSingleValue(Resource.SearchableFields.URI, "uri", op, values); - getter = re -> Optional.ofNullable(re.getUris()).orElseGet(Collections::emptySet).contains(uri); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkResourceScopes(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - - if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { - Collection c = (Collection) values[0]; - getter = re -> Optional.ofNullable(re.getScopeIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(c::contains); - } else { - String scope = ensureEqSingleValue(Resource.SearchableFields.URI, "scope_id", op, values); - getter = re -> Optional.ofNullable(re.getScopeIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(scope::equals); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkPolicyResources(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - - if (op == Operator.NOT_EXISTS) { - getter = re -> re.getResourceIds() == null || re.getResourceIds().isEmpty(); - } else if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { - Collection c = (Collection) values[0]; - getter = re -> Optional.ofNullable(re.getResourceIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(c::contains); - } else { - String scope = ensureEqSingleValue(Policy.SearchableFields.RESOURCE_ID, "resource_id", op, values, String.class); - getter = re -> Optional.ofNullable(re.getResourceIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(scope::equals); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkPolicyScopes(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - - if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { - Collection c = (Collection) values[0]; - getter = re -> Optional.ofNullable(re.getScopeIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(c::contains); // TODO: Use KeyConverter - } else { - String scope = ensureEqSingleValue(Policy.SearchableFields.CONFIG, "scope_id", op, values); - getter = re -> Optional.ofNullable(re.getScopeIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(scope::equals); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkPolicyConfig(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - - final Object attrName = values[0]; - if (!(attrName instanceof String)) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values)); - } - - String attrNameS = (String) attrName; - Object[] realValues = new Object[values.length - 1]; - System.arraycopy(values, 1, realValues, 0, values.length - 1); - Predicate valueComparator = CriteriaOperator.predicateFor(op, realValues); - getter = pe -> { - final String configValue = pe.getConfig(attrNameS); - return valueComparator.test(configValue); - }; - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkAssociatedPolicy(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - - if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { - Collection c = (Collection) values[0]; - getter = re -> Optional.ofNullable(re.getAssociatedPolicyIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(c::contains); - } else { - String policyId = ensureEqSingleValue(Policy.SearchableFields.ASSOCIATED_POLICY_ID, "associated_policy_id", op, values); - getter = re -> Optional.ofNullable(re.getAssociatedPolicyIds()).orElseGet(Collections::emptySet).stream().map(Object::toString).anyMatch(policyId::equals); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkUserGroup(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - Function getter; - if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { - Collection c = (Collection) values[0]; - getter = ue -> Optional.ofNullable(ue.getGroupsMembership()).orElseGet(Collections::emptySet).stream().anyMatch(c::contains); - } else { - String groupIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_GROUP, "group_id", op, values); - getter = ue -> Optional.ofNullable(ue.getGroupsMembership()).orElseGet(Collections::emptySet).contains(groupIdS); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkUserClientConsent(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String clientIdS = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "client_id", op, values); - Function getter; - getter = ue -> ue.getUserConsent(clientIdS).orElse(null); - - return mcb.fieldCompare(Operator.EXISTS, getter, null); - } - - private static MapModelCriteriaBuilder checkUserConsentsWithClientScope(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String clientScopeIdS = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "client_scope_id", op, values); - Function getter; - getter = ue -> Optional.ofNullable(ue.getUserConsents()).orElseGet(Collections::emptySet).stream().anyMatch(consent -> Optional.ofNullable(consent.getGrantedClientScopesIds()).orElseGet(Collections::emptySet).contains(clientScopeIdS)); - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder getUserIdpAliasAtIdentityProviderPredicate(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - if (op != Operator.EQ) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op); - } - if (values == null || values.length == 0 || values.length > 2) { - throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op, "Invalid arguments, expected (idp_alias) or (idp_alias, idp_user), got: " + Arrays.toString(values)); - } - - final Object idpAlias = values[0]; - Function getter; - if (values.length == 1) { - getter = ue -> Optional.ofNullable(ue.getFederatedIdentities()).orElseGet(Collections::emptySet).stream() - .anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider())); - } else if (idpAlias == null) { - final Object idpUserId = values[1]; - getter = ue -> Optional.ofNullable(ue.getFederatedIdentities()).orElseGet(Collections::emptySet).stream() - .anyMatch(aue -> Objects.equals(idpUserId, aue.getUserId())); - } else { - final Object idpUserId = values[1]; - getter = ue -> Optional.ofNullable(ue.getFederatedIdentities()).orElseGet(Collections::emptySet).stream() - .anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider()) && Objects.equals(idpUserId, aue.getUserId())); - } - - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkRealmsWithComponentType(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String providerType = ensureEqSingleValue(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, "component_provider_type", op, values); - Function getter = realmEntity -> Optional.ofNullable(realmEntity.getComponents()).orElseGet(Collections::emptySet).stream().anyMatch(component -> component.getProviderType().equals(providerType)); - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - private static MapModelCriteriaBuilder checkUserSessionContainsAuthenticatedClientSession(MapModelCriteriaBuilder mcb, Operator op, Object[] values) { - String clientId = ensureEqSingleValue(UserSessionModel.SearchableFields.CLIENT_ID, "client_id", op, values); - Function getter = use -> (use.getAuthenticatedClientSession(clientId).isPresent()); - return mcb.fieldCompare(Boolean.TRUE::equals, getter); - } - - public static Map, UpdatePredicatesFunc> basePredicates(SearchableModelField idField) { - Map, UpdatePredicatesFunc> fieldPredicates = new HashMap<>(); - fieldPredicates.put(idField, MapModelCriteriaBuilder::idCompare); - return fieldPredicates; - } - - public static Comparator getComparator(QueryParameters.OrderBy orderBy) { - SearchableModelField searchableModelField = orderBy.getModelField(); - QueryParameters.Order order = orderBy.getOrder(); - - @SuppressWarnings("unchecked") - Comparator comparator = (Comparator) COMPARATORS.get(searchableModelField); - - if (comparator == null) { - throw new IllegalArgumentException("Comparator for field " + searchableModelField.getName() + " is not configured."); - } - - if (order == QueryParameters.Order.DESCENDING) { - return comparator.reversed(); - } - - return comparator; - } - - @SuppressWarnings("unchecked") - public static Comparator getComparator(Stream> ordering) { - return (Comparator) ordering.map(MapFieldPredicates::getComparator) - .reduce(Comparator::thenComparing) - .orElseThrow(() -> new IllegalArgumentException("Cannot create comparator for " + ordering)); - } - - @SuppressWarnings("unchecked") - public static Map, UpdatePredicatesFunc> getPredicates(Class clazz) { - return PREDICATES.get(clazz); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapModelCriteriaBuilder.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapModelCriteriaBuilder.java deleted file mode 100644 index a9c23343778..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapModelCriteriaBuilder.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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.storage.chm; - -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.storage.SearchableModelField; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -/** - * - * @author hmlnarik - */ -public class MapModelCriteriaBuilder implements ModelCriteriaBuilder> { - - @FunctionalInterface - public interface UpdatePredicatesFunc { - MapModelCriteriaBuilder apply(MapModelCriteriaBuilder builder, Operator op, Object[] params); - } - - protected static final Predicate ALWAYS_TRUE = (e) -> true; - protected static final Predicate ALWAYS_FALSE = (e) -> false; - private final Predicate keyFilter; - private final Predicate entityFilter; - private final Map, UpdatePredicatesFunc> fieldPredicates; - private final StringKeyConverter keyConverter; - - public MapModelCriteriaBuilder(StringKeyConverter keyConverter, Map, UpdatePredicatesFunc> fieldPredicates) { - this(keyConverter, fieldPredicates, ALWAYS_TRUE, ALWAYS_TRUE); - } - - protected MapModelCriteriaBuilder(StringKeyConverter keyConverter, Map, UpdatePredicatesFunc> fieldPredicates, Predicate indexReadFilter, Predicate sequentialReadFilter) { - this.keyConverter = keyConverter; - this.fieldPredicates = fieldPredicates; - this.keyFilter = indexReadFilter; - this.entityFilter = sequentialReadFilter; - } - - @Override - public MapModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... values) { - UpdatePredicatesFunc method = fieldPredicates.get(modelField); - if (method == null) { - throw new IllegalArgumentException("Filter not implemented for field " + modelField); - } - - return method.apply(this, op, values); - } - - @SafeVarargs - @SuppressWarnings("unchecked") - @Override - public final MapModelCriteriaBuilder and(MapModelCriteriaBuilder... builders) { - Predicate resIndexFilter = Stream.of(builders).map(MapModelCriteriaBuilder.class::cast).map(MapModelCriteriaBuilder::getKeyFilter).reduce(keyFilter, Predicate::and); - Predicate resEntityFilter = Stream.of(builders).map(MapModelCriteriaBuilder.class::cast).map(MapModelCriteriaBuilder::getEntityFilter).reduce(entityFilter, Predicate::and); - return instantiateNewInstance(keyConverter, fieldPredicates, resIndexFilter, resEntityFilter); - } - - @SafeVarargs - @SuppressWarnings("unchecked") - @Override - public final MapModelCriteriaBuilder or(MapModelCriteriaBuilder... builders) { - Predicate resIndexFilter = Stream.of(builders).map(MapModelCriteriaBuilder.class::cast).map(MapModelCriteriaBuilder::getKeyFilter).reduce(ALWAYS_FALSE, Predicate::or); - Predicate resEntityFilter = Stream.of(builders).map(MapModelCriteriaBuilder.class::cast).map(MapModelCriteriaBuilder::getEntityFilter).reduce(ALWAYS_FALSE, Predicate::or); - return instantiateNewInstance( - keyConverter, - fieldPredicates, - v -> keyFilter.test(v) && resIndexFilter.test(v), - v -> entityFilter.test(v) && resEntityFilter.test(v) - ); - } - - @Override - public MapModelCriteriaBuilder not(MapModelCriteriaBuilder builder) { - Predicate resIndexFilter = builder.getKeyFilter() == ALWAYS_TRUE ? ALWAYS_TRUE : builder.getKeyFilter().negate(); - Predicate resEntityFilter = builder.getEntityFilter() == ALWAYS_TRUE ? ALWAYS_TRUE : builder.getEntityFilter().negate(); - - return instantiateNewInstance( - keyConverter, - fieldPredicates, - v -> keyFilter.test(v) && resIndexFilter.test(v), - v -> entityFilter.test(v) && resEntityFilter.test(v) - ); - } - - public Predicate getKeyFilter() { - return keyFilter; - } - - public Predicate getEntityFilter() { - return entityFilter; - } - - protected MapModelCriteriaBuilder idCompare(Operator op, Object[] values) { - Object[] convertedValues = convertValuesToKeyType(values); - switch (op) { - case LT: - case LE: - case GT: - case GE: - case EQ: - case NE: - case EXISTS: - case NOT_EXISTS: - case IN: - return instantiateNewInstance(keyConverter, fieldPredicates, this.keyFilter.and(CriteriaOperator.predicateFor(op, convertedValues)), this.entityFilter); - default: - throw new AssertionError("Invalid operator: " + op); - } - } - - protected Object[] convertValuesToKeyType(Object[] values) { - if (values == null) { - return null; - } - Object[] res = new Object[values.length]; - for (int i = 0; i < values.length; i ++) { - Object v = values[i]; - if (v instanceof String) { - res[i] = keyConverter.fromStringSafe((String) v); - } else if (v instanceof Stream) { - res[i] = ((Stream) v).map(o -> (o instanceof String) ? keyConverter.fromStringSafe((String) o) : o); - } else if (v instanceof Collection) { - res[i] = ((List) v).stream().map(o -> (o instanceof String) ? keyConverter.fromStringSafe((String) o) : o).collect(Collectors.toList()); - } else if (v == null) { - res[i] = null; - } else { - throw new IllegalArgumentException("Unknown type: " + v); - } - } - return res; - } - - - protected MapModelCriteriaBuilder fieldCompare(Operator op, Function getter, Object[] values) { - Predicate valueComparator = CriteriaOperator.predicateFor(op, values); - return fieldCompare(valueComparator, getter); - } - - protected MapModelCriteriaBuilder fieldCompare(Predicate valueComparator, Function getter) { - final Predicate resEntityFilter; - if (entityFilter == ALWAYS_FALSE) { - resEntityFilter = ALWAYS_FALSE; - } else { - final Predicate p = v -> valueComparator.test(getter.apply(v)); - resEntityFilter = p.and(entityFilter); - } - return instantiateNewInstance(keyConverter, fieldPredicates, this.keyFilter, resEntityFilter); - } - - /** - * Return a new instance for nodes in this criteria tree. - * - * Subclasses can override this method to instantiate a new instance of their subclass. This allows this class to - * be extendable. - */ - protected MapModelCriteriaBuilder instantiateNewInstance(StringKeyConverter keyConverter, Map, UpdatePredicatesFunc> fieldPredicates, Predicate indexReadFilter, Predicate sequentialReadFilter) { - return new MapModelCriteriaBuilder<>(keyConverter, fieldPredicates, indexReadFilter, sequentialReadFilter); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapCrudOperations.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapCrudOperations.java deleted file mode 100644 index 90695f0fd1b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapCrudOperations.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022 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.storage.chm; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.QueryParameters; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -import java.util.stream.Stream; - -/** - * @author Martin Kanis - */ -public class SingleUseObjectConcurrentHashMapCrudOperations extends ConcurrentHashMapCrudOperations { - - public SingleUseObjectConcurrentHashMapCrudOperations(StringKeyConverter keyConverter, DeepCloner cloner) { - super(SingleUseObjectValueModel.class, keyConverter, cloner); - } - - @Override - public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) { - if (value.getId() == null) { - if (value.getObjectKey() != null) { - value.setId(value.getObjectKey()); - } - } - return super.create(value); - } - - @Override - public Stream read(QueryParameters queryParameters) { - DefaultModelCriteria criteria = queryParameters.getModelCriteriaBuilder(); - - if (criteria == null) { - return Stream.empty(); - } - - SingleUseObjectModelCriteriaBuilder mcb = criteria.flashToModelCriteriaBuilder(createSingleUseObjectCriteriaBuilder()); - if (mcb.isValid()) { - MapSingleUseObjectEntity value = read(mcb.getKey()); - return value != null ? Stream.of(value) : Stream.empty(); - } - - return super.read(queryParameters); - } - - private SingleUseObjectModelCriteriaBuilder createSingleUseObjectCriteriaBuilder() { - return new SingleUseObjectModelCriteriaBuilder(); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectMapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectMapStorage.java deleted file mode 100644 index a062a1481cd..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectMapStorage.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022 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.storage.chm; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; -import org.keycloak.models.map.storage.CrudOperations; -import org.keycloak.storage.SearchableModelField; - -import java.util.Map; - -/** - * @author Martin Kanis - */ -public class SingleUseObjectMapStorage extends ConcurrentHashMapStorage> { - - public SingleUseObjectMapStorage(CrudOperations map, - StringKeyConverter keyConverter, - DeepCloner cloner, - Map, - MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates) { - super(map, keyConverter, cloner, fieldPredicates); - } - - @Override - public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) { - if (value.getId() == null) { - if (value.getObjectKey() != null) { - value.setId(value.getObjectKey()); - } - } - return super.create(value); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectModelCriteriaBuilder.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectModelCriteriaBuilder.java deleted file mode 100644 index 9378dad7b36..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectModelCriteriaBuilder.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 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.storage.chm; - -import org.keycloak.models.SingleUseObjectValueModel; -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.storage.SearchableModelField; - -/** - * @author Martin Kanis - */ -public class SingleUseObjectModelCriteriaBuilder implements ModelCriteriaBuilder { - - private String objectKey; - - public SingleUseObjectModelCriteriaBuilder() { - } - - public SingleUseObjectModelCriteriaBuilder(String objectKey) { - this.objectKey = objectKey; - } - - @Override - public ModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) { - if (modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) { - objectKey = value[0].toString(); - } - return new SingleUseObjectModelCriteriaBuilder(objectKey); - } - - @Override - public ModelCriteriaBuilder and(ModelCriteriaBuilder[] builders) { - String objectKey = null; - - for (ModelCriteriaBuilder builder: builders) { - SingleUseObjectModelCriteriaBuilder suoMcb = (SingleUseObjectModelCriteriaBuilder) builder; - if (suoMcb.objectKey != null) { - objectKey = suoMcb.objectKey; - } - } - return new SingleUseObjectModelCriteriaBuilder(objectKey); - } - - @Override - public ModelCriteriaBuilder or(ModelCriteriaBuilder[] builders) { - throw new IllegalStateException("SingleUseObjectModelCriteriaBuilder does not support OR operation."); - } - - @Override - public ModelCriteriaBuilder not(ModelCriteriaBuilder builder) { - throw new IllegalStateException("SingleUseObjectModelCriteriaBuilder does not support NOT operation."); - } - - public boolean isValid() { - return objectKey != null; - } - - public String getKey() { - return objectKey; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/criteria/DefaultModelCriteria.java b/model/map/src/main/java/org/keycloak/models/map/storage/criteria/DefaultModelCriteria.java deleted file mode 100644 index b57a5689746..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/criteria/DefaultModelCriteria.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 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.storage.criteria; - -/** - * Generic instantiable {@link DescriptiveModelCriteria}. - * @author hmlnarik - */ -public class DefaultModelCriteria extends DescriptiveModelCriteria> { - - private static final DefaultModelCriteria INSTANCE = new DefaultModelCriteria<>(null); - - private DefaultModelCriteria(ModelCriteriaNode node) { - super(node); - } - - @SuppressWarnings("unchecked") - public static DefaultModelCriteria criteria() { - return (DefaultModelCriteria) INSTANCE; - } - - @Override - protected DefaultModelCriteria instantiateForNode(ModelCriteriaNode targetNode) { - return new DefaultModelCriteria<>(targetNode); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/criteria/DescriptiveModelCriteria.java b/model/map/src/main/java/org/keycloak/models/map/storage/criteria/DescriptiveModelCriteria.java deleted file mode 100644 index 6a1a8cd0e97..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/criteria/DescriptiveModelCriteria.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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.storage.criteria; - -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.criteria.ModelCriteriaNode.ExtOperator; -import org.keycloak.storage.SearchableModelField; -import java.util.Collection; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; - -/** - * Descriptive model criteria implementation which in other words represents a Boolean formula on searchable fields. - * @author hmlnarik - */ -public abstract class DescriptiveModelCriteria> implements ModelCriteriaBuilder { - - protected final ModelCriteriaNode node; - - protected DescriptiveModelCriteria(ModelCriteriaNode node) { - this.node = node; - } - - @Override - public Self compare(SearchableModelField modelField, Operator op, Object... value) { - return compare(new ModelCriteriaNode<>(modelField, op, value)); - } - - private Self compare(final ModelCriteriaNode nodeToAdd) { - ModelCriteriaNode targetNode; - - if (isEmpty()) { - targetNode = nodeToAdd; - } else if (node.getNodeOperator() == ExtOperator.AND) { - targetNode = node.cloneTree(); - targetNode.addChild(nodeToAdd); - } else { - targetNode = new ModelCriteriaNode<>(ExtOperator.AND); - targetNode.addChild(node.cloneTree()); - targetNode.addChild(nodeToAdd); - } - - return instantiateForNode(targetNode); - } - - protected abstract Self instantiateForNode(ModelCriteriaNode targetNode); - - @Override - public Self and(Self... mcbs) { - if (mcbs.length == 1) { - return compare(mcbs[0].node); - } - - final ModelCriteriaNode targetNode = new ModelCriteriaNode<>(ExtOperator.AND); - AtomicBoolean hasFalseNode = new AtomicBoolean(false); - for (Self mcb : mcbs) { - final ModelCriteriaNode nodeToAdd = mcb.node; - getNodesToAddForAndOr(nodeToAdd, ExtOperator.AND) - .filter(ModelCriteriaNode::isNotTrueNode) - .peek(n -> { if (n.isFalseNode()) hasFalseNode.lazySet(true); }) - .map(ModelCriteriaNode::cloneTree) - .forEach(targetNode::addChild); - - if (hasFalseNode.get()) { - return compare(new ModelCriteriaNode<>(ExtOperator.__FALSE__)); - } - } - - if (targetNode.getChildren().isEmpty()) { - // AND on empty set of formulae is TRUE: It does hold that there all formulae are satisfied - return compare(new ModelCriteriaNode<>(ExtOperator.__TRUE__)); - } - - return compare(targetNode); - } - - @Override - public Self or(Self... mcbs) { - if (mcbs.length == 1) { - return compare(mcbs[0].node); - } - - final ModelCriteriaNode targetNode = new ModelCriteriaNode<>(ExtOperator.OR); - AtomicBoolean hasTrueNode = new AtomicBoolean(false); - for (Self mcb : mcbs) { - final ModelCriteriaNode nodeToAdd = mcb.node; - getNodesToAddForAndOr(nodeToAdd, ExtOperator.OR) - .filter(ModelCriteriaNode::isNotFalseNode) - .peek(n -> { if (n.isTrueNode()) hasTrueNode.lazySet(true); }) - .map(ModelCriteriaNode::cloneTree) - .forEach(targetNode::addChild); - - if (hasTrueNode.get()) { - return compare(new ModelCriteriaNode<>(ExtOperator.__TRUE__)); - } - } - - if (targetNode.getChildren().isEmpty()) { - // OR on empty set of formulae is FALSE: It does not hold that there is at least one satisfied formula - return compare(new ModelCriteriaNode<>(ExtOperator.__FALSE__)); - } - - return compare(targetNode); - } - - @Override - public Self not(Self mcb) { - ModelCriteriaNode toBeChild = mcb.node; - if (toBeChild.getNodeOperator() == ExtOperator.NOT) { - return compare(toBeChild.getChildren().get(0).cloneTree()); - } - - final ModelCriteriaNode targetNode = new ModelCriteriaNode<>(ExtOperator.NOT); - targetNode.addChild(toBeChild.cloneTree()); - return compare(targetNode); - } - - /** - * Copies contents of this {@code ModelCriteriaBuilder} into - * another {@code ModelCriteriaBuilder}. - * @param mcb {@code ModelCriteriaBuilder} to copy the contents onto - * @return Updated {@code ModelCriteriaBuilder} - */ - public > C flashToModelCriteriaBuilder(C mcb) { - if (isEmpty()) { - return mcb; - } - return mcb == null ? null : node.flashToModelCriteriaBuilder(mcb); - } - - @FunctionalInterface - public interface AtomicFormulaTester { - public Boolean test(SearchableModelField field, Operator operator, Object[] operatorArguments); - } - - public Self partiallyEvaluate(AtomicFormulaTester tester) { - return instantiateForNode(node.cloneTree((field, operator, operatorArguments) -> { - Boolean res = tester.test(field, operator, operatorArguments); - if (res == null) { - return new ModelCriteriaNode<>(field, operator, operatorArguments); - } else { - return new ModelCriteriaNode<>(res ? ExtOperator.__TRUE__ : ExtOperator.__FALSE__); - } - }, ModelCriteriaNode::new)); - } - - /** - * Optimizes this formula into another {@code ModelCriteriaBuilder}, using the values of - * {@link ExtOperator#__TRUE__} and {@link ExtOperator#__FALSE__} accordingly. - * @return New instance of {@code } - */ - public Self optimize() { - return flashToModelCriteriaBuilder(instantiateForNode(null)); - } - - /** - * Returns the realm ID which limits the results of this criteria. - * Does not support formulae which include negation of a condition containing the given field. - * Only supports plain equality ({@link Operator#EQ}), ignores all - * instances of the field comparison which do not use plain equality. - * @return {@code null} if the field is not contained in the formula, there are multiple - * mutually different field values in the formula, or the formula contains field check within - * a negation. - */ - public > Object getSingleRestrictionArgument(String fieldName) { - if (node == null) { - return null; - } - - // relax all conditions but those which check realmId equality. For this moment, - // other operators like NE or IN are disregarded and will be added only if need - // arises, since the current queries do not use them. - DescriptiveModelCriteria criterionFormula = - instantiateForNode(node.cloneTree(n -> { - switch (n.getNodeOperator()) { - case ATOMIC_FORMULA: - if (fieldName.equals(n.getField().getName()) && n.getSimpleOperator() == Operator.EQ) { - return new ModelCriteriaNode<>(n.getField(), n.getSimpleOperator(), n.getSimpleOperatorArguments()); - } - return getNotParentsParity(n.getParent(), true) - ? new ModelCriteriaNode<>(ExtOperator.__TRUE__) - : new ModelCriteriaNode<>(ExtOperator.__FALSE__); - default: - return new ModelCriteriaNode<>(n.getNodeOperator()); - } - })) - .optimize(); - - final ModelCriteriaNode criterionFormulaRoot = criterionFormula.getNode(); - if (criterionFormulaRoot.isFalseNode()) { - return null; - } - - if (criterionFormulaRoot.isTrueNode()) { - return null; - } - - ThreadLocal criterionArgument = new ThreadLocal<>(); - @SuppressWarnings("unchecked") - Optional> firstInvalidNode = criterionFormulaRoot.findFirstDfs(n -> { - switch (n.getNodeOperator()) { - case NOT: - return true; - - case ATOMIC_FORMULA: // Atomic formula must be of the form "realmID" EQ ..., see realmIdFormula instatiation - Object argument = getSingleArgument(n.getSimpleOperatorArguments()); - if (argument != null) { - Object orig = criterionArgument.get(); - if (orig != null && ! Objects.equals(argument, orig)) { - // Two different realms are not supported - return true; - } - criterionArgument.set(argument); - } - return false; - - default: - return false; - } - }); - - return firstInvalidNode.isPresent() ? null : criterionArgument.get(); - } - - private static Object getSingleArgument(Object[] arguments) { - if (arguments == null || arguments.length != 1) { - return null; - } - - final Object a0 = arguments[0]; - if (a0 instanceof Collection) { // Note this cannot be a Stream due to ModelCriteriaNode always converting stream to List - final Collection c0 = (Collection) a0; - return c0.size() == 1 ? c0.iterator().next() : null; - } - - return a0; - } - - public boolean isEmpty() { - return node == null; - } - - public ModelCriteriaNode getNode() { - return node; - } - - @Override - public String toString() { - return isEmpty() ? "" : node.toString(); - } - - private Stream> getNodesToAddForAndOr(ModelCriteriaNode nodeToAdd, ExtOperator operatorBeingAdded) { - final ExtOperator op = nodeToAdd.getNodeOperator(); - - if (op == operatorBeingAdded) { - return nodeToAdd.getChildren().stream(); - } - - return Stream.of(nodeToAdd); - } - - private boolean getNotParentsParity(Optional> node, boolean currentValue) { - return node - .map(n -> getNotParentsParity(n.getParent(), n.getNodeOperator() == ExtOperator.NOT ? ! currentValue : currentValue)) - .orElse(currentValue); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/criteria/ModelCriteriaNode.java b/model/map/src/main/java/org/keycloak/models/map/storage/criteria/ModelCriteriaNode.java deleted file mode 100644 index 48af6c4581b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/criteria/ModelCriteriaNode.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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.storage.criteria; - -import org.keycloak.models.map.storage.ModelCriteriaBuilder; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.tree.DefaultTreeNode; -import org.keycloak.storage.SearchableModelField; -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collections; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * TODO: Introduce separation of parameter values and the structure - * @author hmlnarik - */ -public class ModelCriteriaNode extends DefaultTreeNode> { - - public static enum ExtOperator { - AND { - @Override public > C apply(C mcb, ModelCriteriaNode node) { - if (node.getChildren().isEmpty()) { - return null; - } - final C[] operands = node.getChildren().stream() - .map(n -> n.flashToModelCriteriaBuilder(mcb)) - .filter(Objects::nonNull) - .toArray(n -> (C[]) Array.newInstance(mcb.getClass(), n)); - return operands.length == 0 ? null : mcb.and(operands); - } - @Override public String toString(ModelCriteriaNode node) { - return "(" + node.getChildren().stream().map(ModelCriteriaNode::toString).collect(Collectors.joining(" && ")) + ")"; - } - }, - OR { - @Override public > C apply(C mcb, ModelCriteriaNode node) { - if (node.getChildren().isEmpty()) { - return null; - } - final C[] operands = node.getChildren().stream() - .map(n -> n.flashToModelCriteriaBuilder(mcb)) - .filter(Objects::nonNull) - .toArray(n -> (C[]) Array.newInstance(mcb.getClass(), n)); - return operands.length == 0 ? null : mcb.or(operands); - } - @Override public String toString(ModelCriteriaNode node) { - return "(" + node.getChildren().stream().map(ModelCriteriaNode::toString).collect(Collectors.joining(" || ")) + ")"; - } - }, - NOT { - @Override public > C apply(C mcb, ModelCriteriaNode node) { - final ModelCriteriaNode child = node.getChildren().iterator().next(); - return child.isFalseNode() - ? mcb.and((C[]) Array.newInstance(mcb.getClass(), 0)) - : (child.isTrueNode() - ? mcb.or((C[]) Array.newInstance(mcb.getClass(), 0)) - : mcb.not(child.flashToModelCriteriaBuilder(mcb)) - ); - } - @Override public String toString(ModelCriteriaNode node) { - return "! " + node.getChildren().iterator().next().toString(); - } - }, - ATOMIC_FORMULA { - @Override public > C apply(C mcb, ModelCriteriaNode node) { - return (C) mcb.compare( - node.field, - node.simpleOperator, - node.simpleOperatorArguments - ); - } - @Override public String toString(ModelCriteriaNode node) { - return node.field.getName() + " " + node.simpleOperator + " " + Arrays.deepToString(node.simpleOperatorArguments); - } - }, - __FALSE__ { - @Override public > C apply(C mcb, ModelCriteriaNode node) { - return mcb.or((C[]) Array.newInstance(mcb.getClass(), 0)); - } - @Override public String toString(ModelCriteriaNode node) { - return "__FALSE__"; - } - }, - __TRUE__ { - @Override public > C apply(C mcb, ModelCriteriaNode node) { - return mcb.and((C[]) Array.newInstance(mcb.getClass(), 0)); - } - @Override public String toString(ModelCriteriaNode node) { - return "__TRUE__"; - } - } - ; - - public abstract > C apply(C mcbCreator, ModelCriteriaNode node); - public abstract String toString(ModelCriteriaNode node); - } - - private final ExtOperator nodeOperator; - - private final Operator simpleOperator; - - private final SearchableModelField field; - - private final Object[] simpleOperatorArguments; - - public ModelCriteriaNode(SearchableModelField field, Operator simpleOperator, Object[] simpleOperatorArguments) { - super(Collections.emptyMap()); - this.simpleOperator = simpleOperator; - this.field = field; - this.simpleOperatorArguments = simpleOperatorArguments; - this.nodeOperator = ExtOperator.ATOMIC_FORMULA; - - if (simpleOperatorArguments != null) { - for (int i = 0; i < simpleOperatorArguments.length; i ++) { - Object arg = simpleOperatorArguments[i]; - if (arg instanceof Stream) { - try (Stream sArg = (Stream) arg) { - simpleOperatorArguments[i] = sArg.collect(Collectors.toList()); - } - } - } - } - } - - public ModelCriteriaNode(ExtOperator nodeOperator) { - super(Collections.emptyMap()); - this.nodeOperator = nodeOperator; - this.simpleOperator = null; - this.field = null; - this.simpleOperatorArguments = null; - } - - public ExtOperator getNodeOperator() { - return nodeOperator; - } - - public Operator getSimpleOperator() { - return simpleOperator; - } - - public SearchableModelField getField() { - return field; - } - - public Object[] getSimpleOperatorArguments() { - return simpleOperatorArguments; - } - - public ModelCriteriaNode cloneTree() { - return cloneTree(ModelCriteriaNode::new, ModelCriteriaNode::new); - } - - @FunctionalInterface - public interface AtomicFormulaInstantiator { - public ModelCriteriaNode instantiate(SearchableModelField field, Operator operator, Object[] operatorArguments); - } - - public ModelCriteriaNode cloneTree(AtomicFormulaInstantiator atomicFormulaInstantiator, Function> booleanNodeInstantiator) { - return cloneTree(n -> - n.getNodeOperator() == ExtOperator.ATOMIC_FORMULA - ? atomicFormulaInstantiator.instantiate(n.field, n.simpleOperator, n.simpleOperatorArguments) - : booleanNodeInstantiator.apply(n.nodeOperator) - ); - } - - public boolean isFalseNode() { - return getNodeOperator() == ExtOperator.__FALSE__; - } - - public boolean isNotFalseNode() { - return getNodeOperator() != ExtOperator.__FALSE__; - } - - public boolean isTrueNode() { - return getNodeOperator() == ExtOperator.__TRUE__; - } - - public boolean isNotTrueNode() { - return getNodeOperator() != ExtOperator.__TRUE__; - } - - public > C flashToModelCriteriaBuilder(C mcb) { - final C res = nodeOperator.apply(mcb, this); - return res == null ? mcb : res; - } - - @Override - public String toString() { - return nodeOperator.toString(this); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/DefaultTreeNode.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/DefaultTreeNode.java deleted file mode 100644 index b03e4e15e63..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/DefaultTreeNode.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * 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.storage.tree; - -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; -import java.util.stream.Stream.Builder; - -/** - * Generic implementation of a node in a tree. - *

- * Any method that is not purely on tree or nodes should go into a specialized subclass of this class! - * - * @author hmlnarik - */ -public class DefaultTreeNode> implements TreeNode { - - private static final AtomicInteger COUNTER = new AtomicInteger(); - - protected final Map nodeProperties; - protected final Map edgeProperties; - protected final Map treeProperties; - private final LinkedList children = new LinkedList<>(); - private String id; - private Self parent; - private final int uniqueId = COUNTER.getAndIncrement(); - - /** - * @param treeProperties Reference to tree properties map. Tree properties are maintained outside of this node. - */ - protected DefaultTreeNode(Map treeProperties) { - this.treeProperties = treeProperties == null ? Collections.emptyMap() : treeProperties; - this.edgeProperties = new HashMap<>(); - this.nodeProperties = new HashMap<>(); - } - - public DefaultTreeNode(Map nodeProperties, Map edgeProperties, Map treeProperties) { - this.treeProperties = treeProperties == null ? Collections.emptyMap() : treeProperties; - this.edgeProperties = edgeProperties == null ? new HashMap<>() : edgeProperties; - this.nodeProperties = nodeProperties == null ? new HashMap<>() : nodeProperties; - } - - @Override - public Map getEdgeProperties() { - return this.edgeProperties; - } - - @Override - public Optional getEdgeProperty(String key, Class clazz) { - final Object v = getEdgeProperties().get(key); - return clazz.isInstance(v) ? Optional.of(clazz.cast(v)) : Optional.empty(); - } - - public void setEdgeProperty(String property, Object value) { - this.edgeProperties.put(property, value); - } - - @Override - public Map getNodeProperties() { - return this.nodeProperties; - } - - @Override - public Optional getNodeProperty(String key, Class clazz) { - final Object v = getNodeProperties().get(key); - return clazz.isInstance(v) ? Optional.of(clazz.cast(v)) : Optional.empty(); - } - - public void setNodeProperty(String property, Object value) { - this.nodeProperties.put(property, value); - } - - @Override - public Map getTreeProperties() { - return this.treeProperties; - } - - @Override - public Optional getTreeProperty(String key, Class clazz) { - final Object v = getTreeProperties().get(key); - return clazz.isInstance(v) ? Optional.of(clazz.cast(v)) : Optional.empty(); - } - - @Override - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; - } - - @Override - public Optional findFirstDfs(Predicate visitor) { - Deque stack = new LinkedList<>(); - stack.add(getThis()); - while (! stack.isEmpty()) { - Self node = stack.pop(); - if (visitor.test(node)) { - return Optional.of(node); - } - List c = node.getChildren(); - for (ListIterator li = c.listIterator(c.size()); li.hasPrevious(); ) { - stack.push(li.previous()); - } - } - - return Optional.empty(); - } - - @Override - public Optional findFirstBottommostDfs(Predicate visitor) { - Deque stack = new LinkedList<>(); - stack.add(getThis()); - while (! stack.isEmpty()) { - Self node = stack.pop(); - if (visitor.test(node)) { - // find the bottommost, i.e. inspect children first before returning this node - for (Self child : node.getChildren()) { - Optional childRes = child.findFirstBottommostDfs(visitor); - if (childRes.isPresent()) { - return childRes; - } - } - return Optional.of(node); - } - List c = node.getChildren(); - for (ListIterator li = c.listIterator(c.size()); li.hasPrevious(); ) { - stack.push(li.previous()); - } - } - - return Optional.empty(); - } - - @Override - public Optional findFirstBfs(Predicate visitor) { - Queue queue = new LinkedList<>(); - queue.add(getThis()); - while (! queue.isEmpty()) { - Self node = queue.poll(); - if (visitor.test(node)) { - return Optional.of(node); - } - queue.addAll(node.getChildren()); - } - - return Optional.empty(); - } - - @Override - public void walkBfs(Consumer visitor) { - Queue queue = new LinkedList<>(); - queue.add(getThis()); - while (! queue.isEmpty()) { - Self node = queue.poll(); - visitor.accept(node); - queue.addAll(node.getChildren()); - } - } - - @Override - public void walkDfs(Consumer visitorUponEntry, Consumer visitorAfterChildrenVisited) { - if (visitorUponEntry != null) { - visitorUponEntry.accept(getThis()); - } - for (Self child : children) { - child.walkDfs(visitorUponEntry, visitorAfterChildrenVisited); - } - if (visitorAfterChildrenVisited != null) { - visitorAfterChildrenVisited.accept(getThis()); - } - } - - @Override - public void forEachParent(Consumer visitor) { - for (Optional p = getParent(); p.isPresent(); p = p.get().getParent()) { - visitor.accept(p.get()); - } - } - - @Override - public List getPathToRoot(PathOrientation orientation) { - LinkedList res = new LinkedList<>(); - Consumer addFunc = orientation == PathOrientation.BOTTOM_FIRST ? res::addLast : res::addFirst; - Optional p = Optional.of(getThis()); - while (p.isPresent()) { - addFunc.accept(p.get()); - p = p.get().getParent(); - } - return res; - } - - @Override - public List getChildren() { - return Collections.unmodifiableList(this.children); - } - - public boolean hasChildren() { - return ! this.children.isEmpty(); - } - - public boolean hasNoChildren() { - return this.children.isEmpty(); - } - - @Override - public void addChild(Self node) { - if (node == null) { - return; - } - if (! this.children.contains(node)) { - this.children.add(node); - } - node.setParent(getThis()); - - // Prevent setting a parent of this node as a child of this node. In such a case, remove the parent of this node - for (Optional p = getParent(); p.isPresent(); p = p.get().getParent()) { - if (p.get() == node) { - setParent(null); - return; - } - } - } - - @Override - public void addChild(int index, Self node) { - if (node == null) { - return; - } - if (! this.children.contains(node)) { - this.children.add(index, node); - } - node.setParent(getThis()); - - // Prevent setting a parent of this node as a child of this node. In such a case, remove the parent of this node - for (Optional p = getParent(); p.isPresent(); p = p.get().getParent()) { - if (p.get() == node) { - setParent(null); - return; - } - } - } - - @Override - public Optional getChild(String id) { - for (Self c : children) { - if (id.equals(c.getId())) { - return Optional.of(c); - } - } - return Optional.empty(); - } - - @Override - public int removeChild(Predicate shouldRemove) { - if (shouldRemove == null) { - return 0; - } - int res = 0; - for (Iterator it = children.iterator(); it.hasNext();) { - Self n = it.next(); - if (shouldRemove.test(n)) { - it.remove(); - n.setParent(null); - res++; - } - } - return res; - } - - @Override - public Optional removeChild(Self node) { - if (node == null) { - return Optional.empty(); - } - for (Iterator it = children.iterator(); it.hasNext();) { - Self res = it.next(); - if (node.equals(res)) { - it.remove(); - res.setParent(null); - return Optional.of(res); - } - } - return Optional.empty(); - } - - @Override - public Optional getParent() { - return Optional.ofNullable(this.parent); - } - - @Override - public void setParent(Self parent) { - if (this.parent == parent) { - return; - } - if (parent == this) { - setParent(null); - } - - if (this.parent != null) { - Self previousParent = this.parent; - this.parent = null; - previousParent.removeChild(getThis()); - } - - if (parent != null) { - this.parent = parent; - parent.addChild(getThis()); - } - } - - public > RNode cloneTree(Function instantiateFunc) { - final RNode res = instantiateFunc.apply(getThis()); - this.getChildren().forEach(c -> res.addChild(c.cloneTree(instantiateFunc))); - return res; - } - - @SuppressWarnings("unchecked") - private Self getThis() { - return (Self) this; - } - - @Override - public int hashCode() { - return this.uniqueId; - } - - @Override - public boolean equals(Object obj) { - return this == obj; - } - - @Override - public Stream getParentsStream() { - Builder resBuilder = Stream.builder(); - for (Optional p = getParent(); p.isPresent(); p = p.get().getParent()) { - resBuilder.accept(p.get()); - } - return resBuilder.build(); - } - - private static final ThreadLocal TOSTRING_DETAILS = new ThreadLocal() { - @Override - protected Boolean initialValue() { - return Boolean.TRUE; - } - - }; - - /** - * Print a tree structure in a pretty ASCII format. - * Adopted from https://stackoverflow.com/a/53705889/6930869 - * - * @param prefix Current prefix. Use "" in initial call - * @param node The current node - * @param getChildrenFunc A {@link Function} that returns the children of a given node. - * @param isTail Is node the last of its siblings. Use true in initial call. (This is needed for pretty printing.) - * @param The type of your nodes. Anything that has a toString can be used. - */ - private static StringBuilder toString(StringBuilder output, String prefix, DefaultTreeNode node, boolean isTail) { - String nodeName = node.getLabel(); - if (Objects.equals(TOSTRING_DETAILS.get(), Boolean.FALSE)) { - return new StringBuilder("@").append(nodeName); - } - String nodeConnection = isTail ? (prefix.isEmpty() ? "O── " : "└── ") : "├── "; - output.append(prefix).append(nodeConnection).append(nodeName); - try { - TOSTRING_DETAILS.set(Boolean.FALSE); - output.append(node.getNodeProperties().isEmpty() ? "" : " " + node.getNodeProperties()); - } finally { - TOSTRING_DETAILS.set(Boolean.TRUE); - } - output.append(System.lineSeparator()); - List> ch = node.getChildren(); - for (int i = 0; i < ch.size(); i ++) { - String newPrefix = prefix + (isTail ? " " : "│ "); - toString(output, newPrefix, ch.get(i), i == ch.size() - 1); - } - return output; - } - - protected String getLabel() { - return getId(); - } - - @Override - public String toString() { - return toString(new StringBuilder(), "", getThis(), true).toString(); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/EmptyMapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/EmptyMapStorage.java deleted file mode 100644 index 99ca093d308..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/EmptyMapStorage.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.storage.tree; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.QueryParameters; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -public class EmptyMapStorage { - - public static MapStorage getInstance() { - return new MapStorage<>() { - @Override - public V create(V value) { - return null; - } - - @Override - public V read(String key) { - return null; - } - - @Override - public Stream read(QueryParameters queryParameters) { - return Stream.empty(); - } - - @Override - public long getCount(QueryParameters queryParameters) { - return 0; - } - - @Override - public boolean delete(String key) { - return false; - } - - @Override - public long delete(QueryParameters queryParameters) { - return 0; - } - }; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/NodeProperties.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/NodeProperties.java deleted file mode 100644 index 44b2351ce85..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/NodeProperties.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.storage.tree; - -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -/** - * - * @author hmlnarik - */ -public final class NodeProperties { - - /** - * Defines the filter that must be satisfied for every entity within this store. - * Type: {@link DefaultModelCriteria} - */ - public static final String ENTITY_RESTRICTION = "entity-restriction"; - public static final String AUTHORITATIVE_DECIDER = "authoritative-decider"; - public static final String STRONGLY_AUTHORITATIVE = "strongly-authoritative"; - public static final String READ_ONLY = "read-only"; - public static final String REVALIDATE = "revalidate"; - - public static final String AUTHORITATIVE_NODES = "___authoritative-nodes___"; - public static final String STORAGE_PROVIDER = "___storage-provider___"; - public static final String STORAGE_SUPPLIER = "___storage-supplier___"; - - /** - * Map of pairs ({@code k}: {@link EntityField}, {@code v}: {@link Collection}) of fields that the node is primary source for. - *

- * For example, the following statements are expressed: - *

    - *
  • {@code (name -> null)}: This node is primary source for the value of the field {@code name}. - *
  • {@code (attributes -> null)}: This node is primary source for the values of all attributes. - *
  • {@code (attributes -> {"address", "logo"})}: This node is primary source only for the values of attributes "address" and "logo". - *
- */ - public static final String PRIMARY_SOURCE_FOR = "___primary-source-for___"; - - /** - * Map of pairs ({@code k}: {@link EntityField}, {@code v}: {@link Collection}) of fields that the node is not primary source for. - *

- * For example, the following statements are expressed: - *

    - *
  • {@code (name -> null)}: This node is not primary source for the value of the field {@code name}. - *
  • {@code (attributes -> null)}: This node is not primary source for the values of any attributes. - *
  • {@code (attributes -> {"address", "logo"})}: This node is primary source only for attributes apart from "address" and "logo" attributes. - *
- */ - public static final String PRIMARY_SOURCE_FOR_EXCLUDED = "___primary-source-for-excluded___"; - - /** - * Map of pairs ({@code k}: {@link EntityField}, {@code v}: {@link Collection}) of fields that the node is primary source for. - *

- * For example, the following statements are expressed: - *

    - *
  • {@code (name -> null)}: This node is primary source for the value of the field {@code name}. - *
  • {@code (attributes -> null)}: This node is primary source for the values of all attributes. - *
  • {@code (attributes -> {"address", "logo"})}: This node is primary source only for the values of attributes "address" and "logo". - *
- */ - public static final String CACHE_FOR = "___cache-for___"; - - /** - * Map of pairs ({@code k}: {@link EntityField}, {@code v}: {@link Collection}) of fields that the node is not primary source for. - *

- * For example, the following statements are expressed: - *

    - *
  • {@code (name -> null)}: This node is not primary source for the value of the field {@code name}. - *
  • {@code (attributes -> null)}: This node is not primary source for the values of any attributes. - *
  • {@code (attributes -> {"address", "logo"})}: This node is primary source only for attributes apart from "address" and "logo" attributes. - *
- */ - public static final String CACHE_FOR_EXCLUDED = "___cache-for-excluded___"; - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeNode.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeNode.java deleted file mode 100644 index 430328632ce..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeNode.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.storage.tree; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -/** - * Interface representing a node in a tree that has ID. - *

- * Think twice when adding a method here: if added method does not operate purely - * on nodes or a generic tree, it does not belong here. - * @author hmlnarik - */ -public interface TreeNode> { - - public enum PathOrientation { BOTTOM_FIRST, TOP_FIRST } - - /** - * Adds a node as a child of this node, and sets the parent of the {@code node} to this node. - * @param node Future child node. If {@code null} or the node is already amongst the children list, no action is done. - */ - void addChild(Self node); - - /** - * Adds a node as a child of this node, and sets the parent of the {@code node} to this node. - * @param index Index at which the specified element is to be inserted - * @param node Future child node. If {@code null} or the node is already amongst the children list, no action is done. - * - */ - void addChild(int index, Self node); - - /** - * Returns a node by ID. If there are more nodes with the same ID, any node from those may be returned. - * @param id - * @return - */ - Optional getChild(String id); - - /** - * Returns the children of the current node. Order does matter. - * @return Read-only list of the children. Never returns {@code null}. - */ - List getChildren(); - - /** - * Parent-to-this-node edge properties. For example, import/no-import mode or sync mode belongs here. - * @return Returns properties of the edge from the parent to this node. Never returns {@code null}. - */ - Map getEdgeProperties(); - - /** - * Convenience method for obtaining a single parent-to-this-node edge property. - * @param - * @param key - * @param clazz - * @return {@code Optional} with a property value if it exists. Never returns {@code null} - */ - Optional getEdgeProperty(String key, Class clazz); - - /** - * Returns ID of the node, which could match e.g. ID of the component with storage definition. - * @return Node ID - */ - String getId(); - - /** - * Properties of the this node. In storage context, properties of the single map storage represented by this - * node, for example read-only/read-write flag. - * @return Returns properties of the storage managed in this node. Never returns {@code null}. - */ - Map getNodeProperties(); - - /** - * Convenience method for obtaining a single property of this node. - * @param - * @param key - * @param clazz - * @return {@code Optional} with a property value if it exists. Never returns {@code null} - */ - Optional getNodeProperty(String key, Class clazz); - - /** - * Properties of the whole tree. For example, kind of the stored objects, e.g. realms or clients. - * @return Returns properties of the tree that contains in this node. Never returns {@code null}. - */ - Map getTreeProperties(); - - /** - * Convenience method for obtaining a single property of tree that this node belongs to. - * @param - * @param key - * @param clazz - * @return {@code Optional} with a property value if it exists. Never returns {@code null} - */ - Optional getTreeProperty(String key, Class clazz); - - /** - * Removes the given child node. - * @param node Node to remove - * @return Removed node - */ - Optional removeChild(Self node); - - /** - * Removes child nodes satisfying the given predicate. - * @param node Predicate on node returning {@code true} for each node that should be removed - * @return Number of removed nodes - */ - int removeChild(Predicate shouldRemove); - - /** - * Returns parent node or an empty {@code Optional} if this node is a root node. - * @return See description. Never returns {@code null}. - */ - Optional getParent(); - - /** - * Sets the parent node to the given {@code parent}. If this node was a child of another node, - * also removes this node from the children of the previous parent. - * @param parent New parent node or {@code null} if this node should be parentless. - */ - void setParent(Self parent); - - /** - * Depth-first search for a node. - * @param visitor Predicate on nodes, returns {@code true} when a search condition is satisfied which terminates the search. - * @return Leftmost first node that matches the predicate, {@code null} when no node matches. - */ - Optional findFirstDfs(Predicate visitor); - - /** - * Depth-first search for a node that is bottommost from those matching DFS. - * @param visitor Predicate on nodes, returns {@code true} when a search condition is satisfied which terminates the search. - * @return Leftmost and bottommost node that matches the predicate, {@code null} when no node matches. - */ - Optional findFirstBottommostDfs(Predicate visitor); - - /** - * Breadth-first search for a node. - * @param visitor Predicate on nodes, returns {@code true} when a search condition is satisfied which terminates the search. - * @return First node that matches the predicate, {@code null} when no node matches. - */ - Optional findFirstBfs(Predicate visitor); - - /** - * Returns the path (list of nodes) from this node to root node. - * @param orientation Determines order of the nodes in the returned list - either this node is first and the root node - * is last, ({@link PathOrientation#BOTTOM_FIRST}) or vice versa ({@link PathOrientation#TOP_FIRST}). - * @return - */ - List getPathToRoot(PathOrientation orientation); - - /** - * Returns a stream of the nodes laying on the path from this node (exclusive) to the root of the tree (inclusive). - * @return - */ - Stream getParentsStream(); - - /** - * Calls the given {@code visitor} on each node laying on the path from this node (exclusive) to the root of the tree (inclusive). - * @param visitor - */ - void forEachParent(Consumer visitor); - - /** - * Walks the tree with the given visitor in depth-first search manner. - * @param visitorUponEntry Visitor called upon entry of the node. May be {@code null}, in that case no action is performed. - * @param visitorAfterChildrenVisited Visitor called before exit of the node. May be {@code null}, in that case no action is performed. - */ - void walkDfs(Consumer visitorUponEntry, Consumer visitorAfterChildrenVisited); - - /** - * Walks the tree with the given visitor in breadth-first search manner. - * @param visitor - */ - void walkBfs(Consumer visitor); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeProperties.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeProperties.java deleted file mode 100644 index bc384f77ba5..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeProperties.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.storage.tree; - -/** - * - * @author hmlnarik - */ -public final class TreeProperties { - - public static final String MODEL_CLASS = "model-class"; - public static final String DEFAULT_STORE_CREATE = "default-create"; - public static final String DEFAULT_STORE_READ = "default-read"; - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeStorageNodeInstance.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeStorageNodeInstance.java deleted file mode 100644 index c5c6bdcfca9..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeStorageNodeInstance.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.storage.tree; - -import java.util.HashMap; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.storage.tree.TreeStorageNodePrescription.FieldContainedStatus; - -/** - * Instance of the tree storage that is based on a prescription ({@link TreeStorageNodePrescription}), - * i.e. it provides a map storage instance that can be used for accessing data. - * - * @author hmlnarik - */ -public class TreeStorageNodeInstance - extends DefaultTreeNode> { - - private final KeycloakSession session; - private final TreeStorageNodePrescription prescription; - private final TreeStorageNodeInstance original; // If this node is a subtree, keep reference to the node in the original tree here - - public class WithEntity { - private final V entity; - - public WithEntity(V entity) { - this.entity = entity; - } - - public V getEntity() { - return entity; - } - - public TreeStorageNodeInstance getNode() { - return TreeStorageNodeInstance.this; - } - - } - - public TreeStorageNodeInstance(KeycloakSession session, TreeStorageNodeInstance original) { - super(original.getNodeProperties(), original.getEdgeProperties(), original.getTreeProperties()); - this.original = original; - this.prescription = original.prescription; - this.session = session; - } - - public TreeStorageNodeInstance(KeycloakSession session, TreeStorageNodePrescription prescription) { - super( - prescription.getNodeProperties() == null ? null : new HashMap<>(prescription.getNodeProperties()), - prescription.getEdgeProperties() == null ? null : new HashMap<>(prescription.getEdgeProperties()), - prescription.getTreeProperties() - ); - this.prescription = prescription; - this.session = session; - this.original = null; - } - - public TreeStorageNodeInstance cloneNodeOnly() { - return new TreeStorageNodeInstance<>(session, this.original == null ? this : this.original); - } - - @Override - public String getId() { - return prescription.getId(); - } - - public boolean isReadOnly() { - return prescription.isReadOnly(); - } - - public FieldContainedStatus isCacheFor(EntityField field, Object parameter) { - return prescription.isCacheFor(field, parameter); - } - - public FieldContainedStatus isPrimarySourceFor(Enum> field, Object parameter) { - return prescription.isPrimarySourceFor((EntityField) field, parameter); - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeStorageNodePrescription.java b/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeStorageNodePrescription.java deleted file mode 100644 index 9e0ab77e9ee..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/storage/tree/TreeStorageNodePrescription.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * 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.storage.tree; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; - -/** - * Prescription of the tree storage. This prescription can - * be externalized and contains e.g. details on the particular storage type - * represented by this node, or properties of the node and edge between this - * and the parent storage. - *

- * Realization of this prescription is in {@link TreeStorageNodeInstance}, namely it does not - * contain a map storage instance that can be directly used - * for accessing data. - * - * @author hmlnarik - */ -public class TreeStorageNodePrescription extends DefaultTreeNode { - - private static final Logger LOG = Logger.getLogger(TreeStorageNodePrescription.class); - private final boolean isPrimarySourceForAnything; - private final boolean isPrimarySourceForEverything; - private final boolean isCacheForAnything; - - public static enum FieldContainedStatus { - /** - * Field is fully contained in the storage in this node. - * For example, attribute {@code foo} or field {@code NAME} stored in this node would be {@code FULLY} contained field. - * @see #PARTIALLY - */ - FULLY { - @Override - public FieldContainedStatus minus(FieldContainedStatus s) { - switch (s) { - case FULLY: - return FieldContainedStatus.NOT_CONTAINED; - case PARTIALLY: - return FieldContainedStatus.PARTIALLY; - default: - return FieldContainedStatus.FULLY; - } - } - }, - /** - * Field is contained in the storage in this node but parts of it might be contained in some child node as well. - * For example, a few attributes can be partially supplied from an LDAP, and the rest could be supplied from the - * supplementing JPA node. - *

- * This status is never used in the case of a fully specified field access but can be used for map-like attributes - * where the key is not specified. - */ - PARTIALLY { - @Override - public FieldContainedStatus minus(FieldContainedStatus s) { - switch (s) { - case FULLY: - return FieldContainedStatus.NOT_CONTAINED; - default: - return FieldContainedStatus.PARTIALLY; - } - } - }, - /** Field is not contained in the storage in this node but parts of it might be contained in some child node as well */ - NOT_CONTAINED { - @Override - public FieldContainedStatus minus(FieldContainedStatus s) { - return FieldContainedStatus.NOT_CONTAINED; - } - }; - - /** - * Returns the status of the field if this field status in this node was stripped off the field status {@code s}. - * Specifically, for two nodes in parent/child relationship, {@code parent.minus(child)} answers the question: - * "How much of the field does parent contain that is not contained in the child?" - *

    - *
  • If the field in this node is contained {@link #FULLY} or {@link #PARTIALLY}, and the - * field in the other node is contained {@link #FULLY}, then - * there is no need to store any part of the field in this node, - * i.e. the result is {@link #NOT_CONTAINED}
  • - *
  • If the field in this node is contained {@link #PARTIALLY} and the field in the other node is also only contained {@link #PARTIALLY}, then - * the portions might be disjunct, so it is still necessary to store a part of the field in this node, i.e. the result is {@link #PARTIALLY}
  • - *
  • If the field in this node is {@link #NOT_CONTAINED} at all, then the result remains {@link #NOT_CONTAINED} regardless of the other node status
  • - *
- */ - public abstract FieldContainedStatus minus(FieldContainedStatus s); - /** - * Returns higher of this and the {@code other} field status: {@link #FULLY} > {@link #PARTIALLY} > {@link #NOT_CONTAINED} - */ - public FieldContainedStatus max(FieldContainedStatus other) { - return (other == null || ordinal() < other.ordinal()) ? this : other; - } - public FieldContainedStatus max(Supplier otherFunc) { - if (ordinal() == 0) { - return this; - } - FieldContainedStatus other = otherFunc.get(); - return other == null || ordinal() < other.ordinal() ? this : other; - } - } - - /** - * Map of prescriptions restricted per entity class derived from this prescription. - */ - private final Map, TreeStorageNodePrescription> restricted = new ConcurrentHashMap<>(); - - public TreeStorageNodePrescription(Map treeProperties) { - this(treeProperties, null, null); - } - - public TreeStorageNodePrescription(Map nodeProperties, Map edgeProperties, Map treeProperties) { - super(nodeProperties, edgeProperties, treeProperties); - Map psf = (Map) this.nodeProperties.get(NodeProperties.PRIMARY_SOURCE_FOR); - Map psfe = (Map) this.nodeProperties.get(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED); - isPrimarySourceForAnything = (psf != null && ! psf.isEmpty()) || (psfe != null && ! psfe.isEmpty()); - isPrimarySourceForEverything = (psf == null) && (psfe == null || psfe.isEmpty()); // This could be restricted further - Map cf = (Map) this.nodeProperties.get(NodeProperties.CACHE_FOR); - Map cfe = (Map) this.nodeProperties.get(NodeProperties.CACHE_FOR_EXCLUDED); - isCacheForAnything = (cf != null && ! cf.isEmpty()) || (cfe != null && ! cfe.isEmpty()); - } - - public TreeStorageNodePrescription forEntityClass(Class targetEntityClass) { - return restricted.computeIfAbsent(targetEntityClass, c -> { - HashMap treeProperties = new HashMap<>(restrictConfigMap(targetEntityClass, getTreeProperties())); - treeProperties.put(TreeProperties.MODEL_CLASS, ModelEntityUtil.getModelType(targetEntityClass)); - return cloneTree(n -> n.forEntityClass(targetEntityClass, treeProperties)); - }); - } - - public TreeStorageNodeInstance instantiate(KeycloakSession session) { - return cloneTree(n -> new TreeStorageNodeInstance<>(session, n)); - } - - private TreeStorageNodePrescription forEntityClass(Class targetEntityClass, - Map treeProperties) { - Map nodeProperties = restrictConfigMap(targetEntityClass, getNodeProperties()); - Map edgeProperties = restrictConfigMap(targetEntityClass, getEdgeProperties()); - - if (nodeProperties == null || edgeProperties == null) { - LOG.debugf("Cannot restrict storage for %s from node: %s", targetEntityClass, this); - return null; - } - - return new TreeStorageNodePrescription(nodeProperties, edgeProperties, treeProperties); - } - - /** - * Restricts configuration map to options that are either applicable everywhere (have no '[' in their name) - * or apply to a selected entity class (e.g. ends in "[clients]" for {@code MapClientEntity}). - * @return A new configuration map crafted for this particular entity class - */ - private static Map restrictConfigMap(Class targetEntityClass, Map np) { - final Class modelType = ModelEntityUtil.getModelType(targetEntityClass, null); - String name = Optional.ofNullable(modelType) - .map(ModelEntityUtil::getModelName) - .orElse(null); - - if (name == null) { - return null; - } - - // Start with all properties not specific for any domain - Map res = np.entrySet().stream() - .filter(me -> me.getKey().indexOf('[') == -1) - .filter(me -> me.getValue() != null) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (s, a) -> s, HashMap::new)); - - // Now add and/or replace properties specific for the target domain - Pattern specificPropertyPattern = Pattern.compile("(.*?)\\[.*\\b" + Pattern.quote(name) + "\\b.*\\]"); - np.keySet().stream() - .map(specificPropertyPattern::matcher) - .filter(Matcher::matches) - .forEach(m -> res.put(m.group(1), np.get(m.group(0)))); - - return res; - } - - public boolean isReadOnly() { - return getNodeProperty(NodeProperties.READ_ONLY, Boolean.class).orElse(false); - } - - /** - * Returns if the given field is primary source for the field, potentially specified further by a parameter. - * - * @param field Field - * @param parameter First parameter, which in case of maps is the key to that map, e.g. attribute name. - * @return For a fully specified field and a parameter (e.g. "attribute" and "attr1"), or a parameterless field (e.g. "client_id"), - * returns either {@code FULLY} or {@code NOT_CONTAINED}. May also return {@code PARTIAL} for a field that requires - * a parameter but the parameter is not specified. - */ - public FieldContainedStatus isPrimarySourceFor(EntityField field, Object parameter) { - if (isPrimarySourceForEverything) { - return FieldContainedStatus.FULLY; - } - if (! isPrimarySourceForAnything) { - return FieldContainedStatus.NOT_CONTAINED; - } - - FieldContainedStatus isPrimarySourceFor = getNodeProperty(NodeProperties.PRIMARY_SOURCE_FOR, Map.class) - .map(m -> isFieldWithParameterIncludedInMap(m, field, parameter)) - .orElse(FieldContainedStatus.FULLY); - - FieldContainedStatus isExcludedPrimarySourceFor = getNodeProperty(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, Map.class) - .map(m -> isFieldWithParameterIncludedInMap(m, field, parameter)) - .orElse(FieldContainedStatus.NOT_CONTAINED); - - return isPrimarySourceFor.minus(isExcludedPrimarySourceFor); - } - - public FieldContainedStatus isCacheFor(EntityField field, Object parameter) { - if (! isCacheForAnything) { - return FieldContainedStatus.NOT_CONTAINED; - } - - // If there is CACHE_FOR_EXCLUDED then this node is a cache for all fields, and should be treated as such even if there is no CACHE_FOR - // This is analogous to PRIMARY_SOURCE_FOR(_EXCLUDED). - // However if there is neither CACHE_FOR_EXCLUDED nor CACHE_FOR, then this node is not a cache - final Optional cacheForExcluded = getNodeProperty(NodeProperties.CACHE_FOR_EXCLUDED, Map.class); - - FieldContainedStatus isCacheFor = getNodeProperty(NodeProperties.CACHE_FOR, Map.class) - .map(m -> isFieldWithParameterIncludedInMap(m, field, parameter)) - .orElse(cacheForExcluded.isPresent() ? FieldContainedStatus.FULLY : FieldContainedStatus.NOT_CONTAINED); - - FieldContainedStatus isExcludedCacheFor = cacheForExcluded - .map(m -> isFieldWithParameterIncludedInMap(m, field, parameter)) - .orElse(FieldContainedStatus.NOT_CONTAINED); - - return isCacheFor.minus(isExcludedCacheFor); - } - - private FieldContainedStatus isFieldWithParameterIncludedInMap(Map field2possibleParameters, EntityField field, Object parameter) { - Collection specificCases = (Collection) field2possibleParameters.get(field); - if (specificCases == null) { - return field2possibleParameters.containsKey(field) - ? FieldContainedStatus.FULLY - : FieldContainedStatus.NOT_CONTAINED; - } else { - return parameter == null - ? FieldContainedStatus.PARTIALLY - : specificCases.contains(parameter) - ? FieldContainedStatus.FULLY - : FieldContainedStatus.NOT_CONTAINED; - } - } - - @Override - protected String getLabel() { - return getId() + getNodeProperty(NodeProperties.STORAGE_PROVIDER, String.class).map(s -> " [" + s + "]").orElse(""); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/AbstractUserModel.java b/model/map/src/main/java/org/keycloak/models/map/user/AbstractUserModel.java deleted file mode 100644 index db58f8b27ce..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/AbstractUserModel.java +++ /dev/null @@ -1,56 +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.user; - -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -public abstract class AbstractUserModel implements UserModel { - - protected final KeycloakSession session; - protected final RealmModel realm; - protected final E entity; - - public AbstractUserModel(KeycloakSession session, RealmModel realm, E entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof UserModel)) return false; - - UserModel that = (UserModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapCredentialValidationOutput.java b/model/map/src/main/java/org/keycloak/models/map/user/MapCredentialValidationOutput.java deleted file mode 100644 index 468909c3fc4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapCredentialValidationOutput.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2022 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.user; - -import org.keycloak.models.CredentialValidationOutput; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Output of a credential validation. - * - * @author Marek Posolda - */ -public class MapCredentialValidationOutput { - - private final V authenticatedUser; - private final CredentialValidationOutput.Status authStatus; // status whether user is authenticated or more steps needed - private final Map state; // Additional state related to authentication. It can contain data to be sent back to client or data about used credentials. - - public MapCredentialValidationOutput(V authenticatedUser, CredentialValidationOutput.Status authStatus, Map state) { - this.authenticatedUser = authenticatedUser; - this.authStatus = authStatus; - this.state = state; - } - - public static MapCredentialValidationOutput failed() { - return new MapCredentialValidationOutput(null, CredentialValidationOutput.Status.FAILED, Collections.emptyMap()); - } - - public V getAuthenticatedUser() { - return authenticatedUser; - } - - public CredentialValidationOutput.Status getAuthStatus() { - return authStatus; - } - - /** - * State that is passed back by provider - */ - public Map getState() { - return state; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserAdapter.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserAdapter.java deleted file mode 100644 index 5113a220a61..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserAdapter.java +++ /dev/null @@ -1,338 +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.user; - -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.common.util.ObjectUtil; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.RoleUtils; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -public abstract class MapUserAdapter extends AbstractUserModel { - public MapUserAdapter(KeycloakSession session, RealmModel realm, MapUserEntity entity) { - super(session, realm, entity); - } - - @Override - public String getId() { - return entity.getId(); - } - - /** - * @return username. Letter case is determined by a realm setting. - */ - @Override - public String getUsername() { - return KeycloakModelUtils.isUsernameCaseSensitive(realm) ? entity.getUsername() : entity.getUsername().toLowerCase(); - } - - @Override - public void setUsername(String username) { - // Do not continue if current username of entity is the requested username - if (username != null && username.equals(entity.getUsername())) return; - - if (checkUsernameUniqueness(realm, username)) { - throw new ModelDuplicateException("A user with username " + username + " already exists"); - } - - entity.setUsername(username); - } - - @Override - public Long getCreatedTimestamp() { - return entity.getCreatedTimestamp(); - } - - @Override - public void setCreatedTimestamp(Long timestamp) { - entity.setCreatedTimestamp(timestamp); - } - - @Override - public boolean isEnabled() { - Boolean enabled = entity.isEnabled(); - return enabled != null && enabled; - } - - @Override - public void setEnabled(boolean enabled) { - entity.setEnabled(enabled); - } - - private Optional getSpecialAttributeValue(String name) { - if (UserModel.FIRST_NAME.equals(name)) { - return Optional.ofNullable(entity.getFirstName()); - } else if (UserModel.LAST_NAME.equals(name)) { - return Optional.ofNullable(entity.getLastName()); - } else if (UserModel.EMAIL.equals(name)) { - return Optional.ofNullable(entity.getEmail()); - } else if (UserModel.USERNAME.equals(name)) { - return Optional.ofNullable(entity.getUsername()); - } - - return Optional.empty(); - } - - private boolean setSpecialAttributeValue(String name, String value) { - if (UserModel.FIRST_NAME.equals(name)) { - entity.setFirstName(value); - return true; - } else if (UserModel.LAST_NAME.equals(name)) { - entity.setLastName(value); - return true; - } else if (UserModel.EMAIL.equals(name)) { - setEmail(value); - return true; - } else if (UserModel.USERNAME.equals(name)) { - setUsername(value); - return true; - } - - return false; - } - - @Override - public void setSingleAttribute(String name, String value) { - if (setSpecialAttributeValue(name, value)) return; - if (value == null) { - entity.removeAttribute(name); - return; - } - entity.setAttribute(name, Collections.singletonList(value)); - } - - @Override - public void setAttribute(String name, List values) { - String valueToSet = (values != null && values.size() > 0) ? values.get(0) : null; - if (setSpecialAttributeValue(name, valueToSet)) return; - - entity.removeAttribute(name); - if (valueToSet == null) { - return; - } - - entity.setAttribute(name, values); - } - - @Override - public void removeAttribute(String name) { - entity.removeAttribute(name); - } - - @Override - public String getFirstAttribute(String name) { - return getSpecialAttributeValue(name) - .orElseGet(() -> Optional.ofNullable(entity.getAttribute(name)).orElseGet(Collections::emptyList).stream().findFirst() - .orElse(null)); - } - - @Override - public Stream getAttributeStream(String name) { - return getSpecialAttributeValue(name).map(Collections::singletonList) - .orElseGet(() -> Optional.ofNullable(entity.getAttribute(name)).orElseGet(Collections::emptyList)).stream(); - } - - @Override - public Map> getAttributes() { - Map> attributes = entity.getAttributes(); - MultivaluedHashMap result = attributes == null ? new MultivaluedHashMap<>() : new MultivaluedHashMap<>(attributes); - result.add(UserModel.FIRST_NAME, entity.getFirstName()); - result.add(UserModel.LAST_NAME, entity.getLastName()); - result.add(UserModel.EMAIL, entity.getEmail()); - result.add(UserModel.USERNAME, entity.getUsername()); - - return result; - } - - @Override - public Stream getRequiredActionsStream() { - Set requiredActions = entity.getRequiredActions(); - return requiredActions == null ? Stream.empty() : requiredActions.stream(); - } - - @Override - public void addRequiredAction(String action) { - entity.addRequiredAction(action); - } - - @Override - public void removeRequiredAction(String action) { - entity.removeRequiredAction(action); - } - - @Override - public String getFirstName() { - return entity.getFirstName(); - } - - @Override - public void setFirstName(String firstName) { - entity.setFirstName(firstName); - } - - @Override - public String getLastName() { - return entity.getLastName(); - } - - @Override - public void setLastName(String lastName) { - entity.setLastName(lastName); - } - - @Override - public String getEmail() { - return entity.getEmail(); - } - - @Override - public void setEmail(String email) { - email = KeycloakModelUtils.toLowerCaseSafe(email); - if (email != null) { - if (email.equals(entity.getEmail())) { - return; - } - if (ObjectUtil.isBlank(email)) { - email = null; - } - } - boolean duplicatesAllowed = realm.isDuplicateEmailsAllowed(); - - if (!duplicatesAllowed && email != null && checkEmailUniqueness(realm, email)) { - throw new ModelDuplicateException("A user with email " + email + " already exists"); - } - - entity.setEmail(email, duplicatesAllowed); - } - - public abstract boolean checkEmailUniqueness(RealmModel realm, String email); - public abstract boolean checkUsernameUniqueness(RealmModel realm, String username); - - @Override - public boolean isEmailVerified() { - Boolean emailVerified = entity.isEmailVerified(); - return emailVerified != null && emailVerified; - } - - @Override - public void setEmailVerified(boolean verified) { - entity.setEmailVerified(verified); - } - - @Override - public Stream getGroupsStream() { - Set groups = entity.getGroupsMembership(); - if (groups == null || groups.isEmpty()) return Stream.empty(); - return session.groups().getGroupsStream(realm, groups.stream()); - } - - @Override - public void joinGroup(GroupModel group) { - if (RoleUtils.isDirectMember(getGroupsStream(), group)) return; - entity.addGroupsMembership(group.getId()); - } - - @Override - public void leaveGroup(GroupModel group) { - entity.removeGroupsMembership(group.getId()); - } - - @Override - public boolean isMemberOf(GroupModel group) { - return RoleUtils.isMember(getGroupsStream(), group); - } - - @Override - public String getFederationLink() { - return entity.getFederationLink(); - } - - @Override - public void setFederationLink(String link) { - entity.setFederationLink(link); - } - - @Override - public String getServiceAccountClientLink() { - return entity.getServiceAccountClientLink(); - } - - @Override - public void setServiceAccountClientLink(String clientInternalId) { - entity.setServiceAccountClientLink(clientInternalId); - } - - - @Override - public Stream getRealmRoleMappingsStream() { - return getRoleMappingsStream().filter(RoleUtils::isRealmRole); - } - - @Override - public Stream getClientRoleMappingsStream(ClientModel app) { - return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); - } - - @Override - public boolean hasDirectRole(RoleModel role) { - Set roles = entity.getRolesMembership(); - return roles != null && entity.getRolesMembership().contains(role.getId()); - } - - @Override - public boolean hasRole(RoleModel role) { - return RoleUtils.hasRole(getRoleMappingsStream(), role) - || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); - } - - @Override - public void grantRole(RoleModel role) { - entity.addRolesMembership(role.getId()); - } - - @Override - public Stream getRoleMappingsStream() { - Set roles = entity.getRolesMembership(); - if (roles == null || roles.isEmpty()) return Stream.empty(); - return entity.getRolesMembership().stream().map(realm::getRoleById).filter(Objects::nonNull); - } - - @Override - public void deleteRoleMapping(RoleModel role) { - entity.removeRolesMembership(role.getId()); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), hashCode()); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserConsentEntity.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserConsentEntity.java deleted file mode 100644 index 64fb250302f..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserConsentEntity.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.user; - -import org.keycloak.common.util.Time; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.ModelException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserConsentModel; -import org.keycloak.models.map.annotations.CollectionKey; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Objects; -import java.util.Set; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapUserConsentEntity extends UpdatableEntity { - - public static MapUserConsentEntity fromModel(UserConsentModel model) { - long currentTime = Time.currentTimeMillis(); - - MapUserConsentEntity consentEntity = DeepCloner.DUMB_CLONER.newInstance(MapUserConsentEntity.class); - consentEntity.setClientId(model.getClient().getId()); - consentEntity.setCreatedDate(currentTime); - consentEntity.setLastUpdatedDate(currentTime); - - model.getGrantedClientScopes() - .stream() - .map(ClientScopeModel::getId) - .forEach(consentEntity::addGrantedClientScopesId); - - return consentEntity; - } - - public static UserConsentModel toModel(RealmModel realm, MapUserConsentEntity entity) { - if (entity == null) { - return null; - } - - ClientModel client = realm.getClientById(entity.getClientId()); - if (client == null) { - throw new ModelException("Client with id " + entity.getClientId() + " is not available"); - } - UserConsentModel model = new UserConsentModel(client); - model.setCreatedDate(entity.getCreatedDate()); - model.setLastUpdatedDate(entity.getLastUpdatedDate()); - - - Set grantedClientScopesIds = entity.getGrantedClientScopesIds(); - - if (grantedClientScopesIds != null && !grantedClientScopesIds.isEmpty()) { - grantedClientScopesIds.stream() - .map(scopeId -> KeycloakModelUtils.findClientScopeById(realm, client, scopeId)) - .filter(Objects::nonNull) - .forEach(model::addGrantedClientScope); - } - - return model; - } - - @CollectionKey - String getClientId(); - void setClientId(String clientId); - - Set getGrantedClientScopesIds(); - void addGrantedClientScopesId(String scope); - void setGrantedClientScopesIds(Set scopesIds); - void removeGrantedClientScopesId(String scopesId); - - Long getCreatedDate(); - void setCreatedDate(Long createdDate); - - Long getLastUpdatedDate(); - void setLastUpdatedDate(Long lastUpdatedDate); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserCredentialEntity.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserCredentialEntity.java deleted file mode 100644 index d89bcce2ed4..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserCredentialEntity.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.user; - -import org.keycloak.credential.CredentialModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Comparator; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapUserCredentialEntity extends AbstractEntity, UpdatableEntity { - - public static MapUserCredentialEntity fromModel(CredentialModel model) { - MapUserCredentialEntity credentialEntity = DeepCloner.DUMB_CLONER.newInstance(MapUserCredentialEntity.class); - String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); - credentialEntity.setId(id); - credentialEntity.setCreatedDate(model.getCreatedDate()); - credentialEntity.setUserLabel(model.getUserLabel()); - credentialEntity.setType(model.getType()); - credentialEntity.setSecretData(model.getSecretData()); - credentialEntity.setCredentialData(model.getCredentialData()); - - return credentialEntity; - } - - public static CredentialModel toModel(MapUserCredentialEntity entity) { - CredentialModel model = new CredentialModel(); - model.setId(entity.getId()); - model.setType(entity.getType()); - model.setCreatedDate(entity.getCreatedDate()); - model.setUserLabel(entity.getUserLabel()); - model.setSecretData(entity.getSecretData()); - model.setCredentialData(entity.getCredentialData()); - return model; - } - - String getType(); - void setType(String type); - - String getUserLabel(); - void setUserLabel(String userLabel); - - Long getCreatedDate(); - void setCreatedDate(Long createdDate); - - String getSecretData(); - void setSecretData(String secretData); - - String getCredentialData(); - void setCredentialData(String credentialData); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java deleted file mode 100644 index 20db5a7222e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.user; - -import org.jboss.logging.Logger; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityWithAttributes; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.credential.DefaultMapSubjectCredentialManagerEntity; -import org.keycloak.models.map.credential.MapSubjectCredentialManagerEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.user.MapUserEntity.AbstractUserEntity" -) -@DeepCloner.Root -public interface MapUserEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes { - - abstract class AbstractUserEntity extends Impl implements MapUserEntity { - - private static final Logger LOG = Logger.getLogger(MapUserProvider.class); - private String id; - - @Override - public boolean isUpdated() { - return this.updated - || Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserConsentEntity::isUpdated) - || Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).stream().anyMatch(MapUserCredentialEntity::isUpdated) - || Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserFederatedIdentityEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).forEach(UpdatableEntity::clearUpdatedFlag); - Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - - } - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - @Override - public void setEmail(String email, boolean duplicateEmailsAllowed) { - this.setEmail(email); - this.setEmailConstraint(email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email); - } - - @Override - public Boolean moveCredential(String credentialId, String newPreviousCredentialId) { - // 1 - Get all credentials from the entity. - List credentialsList = getCredentials(); - - // 2 - Find indexes of our and newPrevious credential - int ourCredentialIndex = -1; - int newPreviousCredentialIndex = -1; - MapUserCredentialEntity ourCredential = null; - int i = 0; - for (MapUserCredentialEntity credential : credentialsList) { - if (credentialId.equals(credential.getId())) { - ourCredentialIndex = i; - ourCredential = credential; - } else if(newPreviousCredentialId != null && newPreviousCredentialId.equals(credential.getId())) { - newPreviousCredentialIndex = i; - } - i++; - } - - if (ourCredentialIndex == -1) { - LOG.warnf("Not found credential with id [%s] of user [%s]", credentialId, getUsername()); - return false; - } - - if (newPreviousCredentialId != null && newPreviousCredentialIndex == -1) { - LOG.warnf("Can't move up credential with id [%s] of user [%s]", credentialId, getUsername()); - return false; - } - - // 3 - Compute index where we move our credential - int toMoveIndex = newPreviousCredentialId==null ? 0 : newPreviousCredentialIndex + 1; - - // 4 - Insert our credential to new position, remove it from the old position - if (toMoveIndex == ourCredentialIndex) return true; - credentialsList.add(toMoveIndex, ourCredential); - int indexToRemove = toMoveIndex < ourCredentialIndex ? ourCredentialIndex + 1 : ourCredentialIndex; - credentialsList.remove(indexToRemove); - - markUpdatedFlag(); - return true; - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getUsername(); - void setUsername(String username); - - String getFirstName(); - void setFirstName(String firstName); - - Long getCreatedTimestamp(); - void setCreatedTimestamp(Long createdTimestamp); - - String getLastName(); - void setLastName(String lastName); - - String getEmail(); - void setEmail(String email); - @IgnoreForEntityImplementationGenerator - void setEmail(String email, boolean duplicateEmailsAllowed); - - Boolean isEnabled(); - void setEnabled(Boolean enabled); - - Boolean isEmailVerified(); - void setEmailVerified(Boolean emailVerified); - - String getEmailConstraint(); - void setEmailConstraint(String emailConstraint); - - Set getRequiredActions(); - void setRequiredActions(Set requiredActions); - void addRequiredAction(String requiredAction); - void removeRequiredAction(String requiredAction); - - List getCredentials(); - Optional getCredential(String id); - void setCredentials(List credentials); - void addCredential(MapUserCredentialEntity credentialEntity); - Boolean removeCredential(MapUserCredentialEntity credentialEntity); - Boolean removeCredential(String id); - @IgnoreForEntityImplementationGenerator - Boolean moveCredential(String credentialId, String newPreviousCredentialId); - - Set getFederatedIdentities(); - Optional getFederatedIdentity(String identityProviderId); - void setFederatedIdentities(Set federatedIdentities); - void addFederatedIdentity(MapUserFederatedIdentityEntity federatedIdentity); - Boolean removeFederatedIdentity(MapUserFederatedIdentityEntity providerId); - Boolean removeFederatedIdentity(String identityProviderId); - - Set getUserConsents(); - Optional getUserConsent(String clientId); - void setUserConsents(Set userConsentEntity); - void addUserConsent(MapUserConsentEntity userConsentEntity); - Boolean removeUserConsent(MapUserConsentEntity userConsentEntity); - Boolean removeUserConsent(String clientId); - - Set getGroupsMembership(); - void setGroupsMembership(Set groupsMembership); - void addGroupsMembership(String groupId); - void removeGroupsMembership(String groupId); - - Set getRolesMembership(); - void setRolesMembership(Set rolesMembership); - void addRolesMembership(String roleId); - void removeRolesMembership(String roleId); - - String getFederationLink(); - void setFederationLink(String federationLink); - - String getServiceAccountClientLink(); - void setServiceAccountClientLink(String serviceAccountClientLink); - - Long getNotBefore(); - void setNotBefore(Long notBefore); - - default MapSubjectCredentialManagerEntity credentialManager() { - return new DefaultMapSubjectCredentialManagerEntity(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserFederatedIdentityEntity.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserFederatedIdentityEntity.java deleted file mode 100644 index a906a0945ce..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserFederatedIdentityEntity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.user; - -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.map.annotations.CollectionKey; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.UpdatableEntity; - -@GenerateEntityImplementations -@DeepCloner.Root -public interface MapUserFederatedIdentityEntity extends UpdatableEntity { - - public static MapUserFederatedIdentityEntity fromModel(FederatedIdentityModel model) { - if (model == null) return null; - MapUserFederatedIdentityEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapUserFederatedIdentityEntity.class); - entity.setIdentityProvider(model.getIdentityProvider()); - entity.setUserId(model.getUserId()); - entity.setUserName(model.getUserName().toLowerCase()); - entity.setToken(model.getToken()); - - return entity; - } - - public static FederatedIdentityModel toModel(MapUserFederatedIdentityEntity entity) { - if (entity == null) return null; - return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()); - } - - String getToken(); - void setToken(String token); - - String getUserId(); - void setUserId(String userId); - - @CollectionKey - String getIdentityProvider(); - void setIdentityProvider(String identityProvider); - - String getUserName(); - void setUserName(String userName); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java deleted file mode 100644 index cfce47760cc..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java +++ /dev/null @@ -1,808 +0,0 @@ -/* - * Copyright 2022 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.user; - -import org.jboss.logging.Logger; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.common.util.Time; -import org.keycloak.common.util.reflections.Types; -import org.keycloak.component.ComponentModel; -import org.keycloak.credential.CredentialAuthentication; -import org.keycloak.credential.CredentialInput; -import org.keycloak.credential.CredentialProvider; -import org.keycloak.credential.CredentialProviderFactory; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.CredentialValidationOutput; -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.ModelException; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.SubjectCredentialManager; -import org.keycloak.models.UserConsentModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserModel.SearchableFields; -import org.keycloak.models.UserProvider; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.credential.MapUserCredentialManager; -import org.keycloak.models.map.storage.MapStorageWithAuth; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Collection; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.UserModel.EMAIL; -import static org.keycloak.models.UserModel.EMAIL_VERIFIED; -import static org.keycloak.models.UserModel.FIRST_NAME; -import static org.keycloak.models.UserModel.LAST_NAME; -import static org.keycloak.models.UserModel.USERNAME; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_AFTER_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_BEFORE_REMOVE; -import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; -import static org.keycloak.models.utils.KeycloakModelUtils.isUsernameCaseSensitive; - -public class MapUserProvider implements UserProvider { - - private static final Logger LOG = Logger.getLogger(MapUserProvider.class); - private final KeycloakSession session; - final MapStorage store; - private final boolean storeHasRealmId; - - public MapUserProvider(KeycloakSession session, MapStorage store) { - this.session = session; - this.store = store; - this.storeHasRealmId = store instanceof HasRealmId; - } - - private Function entityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return origEntity -> new MapUserAdapter(session, realm, origEntity) { - @Override - public boolean checkEmailUniqueness(RealmModel realm, String email) { - return getUserByEmail(realm, email) != null; - } - - @Override - public boolean checkUsernameUniqueness(RealmModel realm, String username) { - return getUserByUsername(realm, username) != null; - } - - @Override - public SubjectCredentialManager credentialManager() { - return new MapUserCredentialManager(session, realm, this, entity); - } - }; - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) store).setRealmId(realm == null ? null : realm.getId()); - } - return store; - } - - private Predicate entityRealmFilter(RealmModel realm) { - if (realm == null || realm.getId() == null) { - return c -> false; - } - String realmId = realm.getId(); - return entity -> entity.getRealmId() == null || Objects.equals(realmId, entity.getRealmId()); - } - - private ModelException userDoesntExistException() { - return new ModelException("Specified user doesn't exist."); - } - - private Optional getEntityById(RealmModel realm, String id) { - try { - MapUserEntity mapUserEntity = storeWithRealm(realm).read(id); - if (mapUserEntity != null && entityRealmFilter(realm).test(mapUserEntity)) { - return Optional.of(mapUserEntity); - } - - return Optional.empty(); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - - private MapUserEntity getEntityByIdOrThrow(RealmModel realm, String id) { - return getEntityById(realm, id) - .orElseThrow(this::userDoesntExistException); - } - - @Override - public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) { - if (user == null || user.getId() == null) { - return; - } - LOG.tracef("addFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialLink.getIdentityProvider(), getShortStackTrace()); - - getEntityById(realm, user.getId()) - .ifPresent(userEntity -> - userEntity.addFederatedIdentity(MapUserFederatedIdentityEntity.fromModel(socialLink))); - } - - @Override - public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { - LOG.tracef("removeFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace()); - - Optional entityById = getEntityById(realm, user.getId()); - if (!entityById.isPresent()) return false; - - Boolean result = entityById.get().removeFederatedIdentity(socialProvider); - return result == null ? true : result; // TODO: make removeFederatedIdentity return Boolean so the caller can correctly handle "I don't know" null answer - } - - @Override - public void preRemove(RealmModel realm, IdentityProviderModel provider) { - String socialProvider = provider.getAlias(); - LOG.tracef("preRemove[RealmModel realm, IdentityProviderModel provider](%s, %s)%s", realm, socialProvider, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.IDP_AND_USER, Operator.EQ, socialProvider); - - storeWithRealm(realm).read(withCriteria(mcb)) - .forEach(userEntity -> userEntity.removeFederatedIdentity(socialProvider)); - } - - @Override - public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { - LOG.tracef("updateFederatedIdentity(%s, %s, %s)%s", realm, federatedUser.getId(), federatedIdentityModel.getIdentityProvider(), getShortStackTrace()); - getEntityById(realm, federatedUser.getId()) - .flatMap(u -> u.getFederatedIdentity(federatedIdentityModel.getIdentityProvider())) - .ifPresent(fi -> { - fi.setUserId(federatedIdentityModel.getUserId()); - fi.setUserName(federatedIdentityModel.getUserName()); - fi.setToken(federatedIdentityModel.getToken()); - }); - } - - @Override - public Stream getFederatedIdentitiesStream(RealmModel realm, UserModel user) { - LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace()); - return getEntityById(realm, user.getId()) - .map(MapUserEntity::getFederatedIdentities) - .map(Collection::stream) - .orElseGet(Stream::empty) - .map(MapUserFederatedIdentityEntity::toModel); - } - - @Override - public FederatedIdentityModel getFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { - LOG.tracef("getFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace()); - return getEntityById(realm, user.getId()) - .flatMap(userEntity -> userEntity.getFederatedIdentity(socialProvider)) - .map(MapUserFederatedIdentityEntity::toModel) - .orElse(null); - } - - @Override - public UserModel getUserByFederatedIdentity(RealmModel realm, FederatedIdentityModel socialLink) { - LOG.tracef("getUserByFederatedIdentity(%s, %s)%s", realm, socialLink, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.IDP_AND_USER, Operator.EQ, socialLink.getIdentityProvider(), socialLink.getUserId()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .collect(Collectors.collectingAndThen( - Collectors.toList(), - list -> { - if (list.isEmpty()) { - return null; - } else if (list.size() != 1) { - throw new IllegalStateException("More results found for identityProvider=" + socialLink.getIdentityProvider() + - ", userId=" + socialLink.getUserId() + ", results=" + list); - } - - return entityToAdapterFunc(realm).apply(list.get(0)); - })); - } - - @Override - public void addConsent(RealmModel realm, String userId, UserConsentModel consent) { - LOG.tracef("addConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace()); - - getEntityByIdOrThrow(realm, userId) - .addUserConsent(MapUserConsentEntity.fromModel(consent)); - } - - @Override - public UserConsentModel getConsentByClient(RealmModel realm, String userId, String clientInternalId) { - LOG.tracef("getConsentByClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace()); - return getEntityById(realm, userId) - .flatMap(userEntity -> userEntity.getUserConsent(clientInternalId)) - .map(consent -> MapUserConsentEntity.toModel(realm, consent)) - .orElse(null); - } - - @Override - public Stream getConsentsStream(RealmModel realm, String userId) { - LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace()); - return getEntityById(realm, userId) - .map(MapUserEntity::getUserConsents) - .map(Collection::stream) - .orElseGet(Stream::empty) - .map(consent -> MapUserConsentEntity.toModel(realm, consent)); - } - - @Override - public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) { - LOG.tracef("updateConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace()); - - MapUserEntity user = getEntityByIdOrThrow(realm, userId); - MapUserConsentEntity userConsentEntity = user.getUserConsent(consent.getClient().getId()) - .orElseThrow(() -> new ModelException("Consent not found for client [" + consent.getClient().getId() + "] and user [" + userId + "]")); - - userConsentEntity.setGrantedClientScopesIds( - consent.getGrantedClientScopes().stream() - .map(ClientScopeModel::getId) - .collect(Collectors.toSet()) - ); - - userConsentEntity.setLastUpdatedDate(Time.currentTimeMillis()); - } - - @Override - public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) { - LOG.tracef("revokeConsentForClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace()); - - Optional entityById = getEntityById(realm, userId); - if (!entityById.isPresent()) return false; - - Boolean result = entityById.get().removeUserConsent(clientInternalId); - return result == null ? true : result; // TODO: make revokeConsentForClient return Boolean so the caller can correctly handle "I don't know" null answer - } - - @Override - public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) { - LOG.tracef("setNotBeforeForUser(%s, %s, %d)%s", realm, user.getId(), notBefore, getShortStackTrace()); - getEntityByIdOrThrow(realm, user.getId()).setNotBefore(TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(notBefore)); - } - - @Override - public int getNotBeforeOfUser(RealmModel realm, UserModel user) { - LOG.tracef("getNotBeforeOfUser(%s, %s)%s", realm, user.getId(), getShortStackTrace()); - Long notBefore = getEntityById(realm, user.getId()) - .orElseThrow(this::userDoesntExistException) - .getNotBefore(); - - return notBefore == null ? 0 : TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(notBefore); - } - - @Override - public UserModel getServiceAccount(ClientModel client) { - LOG.tracef("getServiceAccount(%s)%s", client.getId(), getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - final RealmModel realm = client.getRealm(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.EQ, client.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .collect(Collectors.collectingAndThen(Collectors.toList(), - list -> { - if (list.isEmpty()) { - return null; - } else if (list.size() != 1) { - throw new IllegalStateException("More service account linked users found for client=" + client.getClientId() + - ", results=" + list); - } - - return entityToAdapterFunc(realm).apply(list.get(0)); - } - )); - } - - @Override - public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - LOG.tracef("addUser(%s, %s, %s, %s, %s)%s", realm, id, username, addDefaultRoles, addDefaultRequiredActions, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(isUsernameCaseSensitive(realm) ? - SearchableFields.USERNAME : - SearchableFields.USERNAME_CASE_INSENSITIVE, Operator.EQ, username); - - if (storeWithRealm(realm).exists(withCriteria(mcb))) { - throw new ModelDuplicateException("User with username '" + username + "' in realm " + realm.getName() + " already exists" ); - } - - if (id != null && storeWithRealm(realm).exists(id)) { - throw new ModelDuplicateException("User exists: " + id); - } - - MapUserEntity entity = DeepCloner.DUMB_CLONER.newInstance(MapUserEntity.class); - entity.setId(id); - entity.setRealmId(realm.getId()); - entity.setEmailConstraint(KeycloakModelUtils.generateId()); - entity.setUsername(username); - entity.setCreatedTimestamp(Time.currentTimeMillis()); - - entity = storeWithRealm(realm).create(entity); - final UserModel userModel = entityToAdapterFunc(realm).apply(entity); - - if (addDefaultRoles) { - userModel.grantRole(realm.getDefaultRole()); - - // No need to check if user has group as it's new user - realm.getDefaultGroupsStream().forEach(userModel::joinGroup); - } - - if (addDefaultRequiredActions){ - realm.getRequiredActionProvidersStream() - .filter(RequiredActionProviderModel::isEnabled) - .filter(RequiredActionProviderModel::isDefaultAction) - .map(RequiredActionProviderModel::getAlias) - .forEach(userModel::addRequiredAction); - } - - return userModel; - } - - @Override - public void preRemove(RealmModel realm) { - LOG.tracef("preRemove[RealmModel](%s)%s", realm, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void removeImportedUsers(RealmModel realm, String storageProviderId) { - LOG.tracef("removeImportedUsers(%s, %s)%s", realm, storageProviderId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void unlinkUsers(RealmModel realm, String storageProviderId) { - LOG.tracef("unlinkUsers(%s, %s)%s", realm, storageProviderId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId); - - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb))) { - s.forEach(userEntity -> userEntity.setFederationLink(null)); - } - } - - @Override - public void preRemove(RealmModel realm, RoleModel role) { - String roleId = role.getId(); - LOG.tracef("preRemove[RoleModel](%s, %s)%s", realm, roleId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, roleId); - - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb))) { - s.forEach(userEntity -> userEntity.removeRolesMembership(roleId)); - } - } - - @Override - public void preRemove(RealmModel realm, GroupModel group) { - String groupId = group.getId(); - LOG.tracef("preRemove[GroupModel](%s, %s)%s", realm, groupId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ASSIGNED_GROUP, Operator.EQ, groupId); - - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb))) { - s.forEach(userEntity -> userEntity.removeGroupsMembership(groupId)); - } - } - - @Override - public void preRemove(RealmModel realm, ClientModel client) { - String clientId = client.getId(); - LOG.tracef("preRemove[ClientModel](%s, %s)%s", realm, clientId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CONSENT_FOR_CLIENT, Operator.EQ, clientId); - - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb))) { - s.forEach(userEntity -> userEntity.removeUserConsent(clientId)); - } - } - - @Override - public void preRemove(ProtocolMapperModel protocolMapper) { - // No-op - } - - @Override - public void preRemove(ClientScopeModel clientScope) { - String clientScopeId = clientScope.getId(); - LOG.tracef("preRemove[ClientScopeModel](%s)%s", clientScopeId, getShortStackTrace()); - - DefaultModelCriteria mcb = criteria(); - final RealmModel realm = clientScope.getRealm(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId); - - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb))) { - s.map(MapUserEntity::getUserConsents) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .forEach(consent -> consent.removeGrantedClientScopesId(clientScopeId)); - } - } - - @Override - public void preRemove(RealmModel realm, ComponentModel component) { - } - - @Override - public void grantToAllUsers(RealmModel realm, RoleModel role) { - String roleId = role.getId(); - LOG.tracef("grantToAllUsers(%s, %s)%s", realm, roleId, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb))) { - s.forEach(entity -> entity.addRolesMembership(roleId)); - } - } - - @Override - public UserModel getUserById(RealmModel realm, String id) { - LOG.tracef("getUserById(%s, %s)%s", realm, id, getShortStackTrace()); - return getEntityById(realm, id).map(entityToAdapterFunc(realm)).orElse(null); - } - - @Override - public UserModel getUserByUsername(RealmModel realm, String username) { - if (username == null) return null; - LOG.tracef("getUserByUsername(%s, %s)%s", realm, username, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(isUsernameCaseSensitive(realm) ? - SearchableFields.USERNAME : - SearchableFields.USERNAME_CASE_INSENSITIVE, Operator.EQ, username); - - // there is orderBy used to always return the same user in case multiple users are returned from the store - try (Stream s = storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.USERNAME, ASCENDING))) { - List users = s.collect(Collectors.toList()); - if (users.isEmpty()) return null; - if (users.size() != 1) { - throw new ModelDuplicateException(String.format("There are colliding usernames for users with usernames and ids: %s", - users.stream().collect(Collectors.toMap(MapUserEntity::getUsername, MapUserEntity::getId)))); - } - return entityToAdapterFunc(realm).apply(users.get(0)); - } - } - - @Override - public UserModel getUserByEmail(RealmModel realm, String email) { - LOG.tracef("getUserByEmail(%s, %s)%s", realm, email, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.EMAIL, Operator.EQ, email); - - List usersWithEmail = storeWithRealm(realm).read(withCriteria(mcb)).collect(Collectors.toList()); - - if (usersWithEmail.isEmpty()) return null; - if (usersWithEmail.size() > 1) { - // Realm settings have been changed from allowing duplicate emails to not allowing them - // but duplicates haven't been removed. - throw new ModelDuplicateException("Multiple users with email '" + email + "' exist in Keycloak."); - } - - MapUserEntity userEntity = usersWithEmail.get(0); - - if (!realm.isDuplicateEmailsAllowed()) { - if (userEntity.getEmail() != null && !userEntity.getEmail().equals(userEntity.getEmailConstraint())) { - // Realm settings have been changed from allowing duplicate emails to not allowing them. - // We need to update the email constraint to reflect this change in the user entities. - userEntity.setEmailConstraint(userEntity.getEmail()); - } - } - - return entityToAdapterFunc(realm).apply(userEntity); - } - - @Override - public int getUsersCount(RealmModel realm, boolean includeServiceAccount) { - LOG.tracef("getUsersCount(%s, %s)%s", realm, includeServiceAccount, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - if (! includeServiceAccount) { - mcb = mcb.compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.NOT_EXISTS); - } - - return (int) storeWithRealm(realm).getCount(withCriteria(mcb)); - } - - private DefaultModelCriteria resolveCriteria(RealmModel realm, Map attributes, DefaultModelCriteria mcb) { - DefaultModelCriteria criteria = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - final boolean exactSearch = Boolean.parseBoolean(attributes.getOrDefault(UserModel.EXACT, Boolean.FALSE.toString())); - - for (Map.Entry entry : attributes.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - if (value == null) { - continue; - } - value = value.trim(); - - final String searchedString = exactSearch ? value : ("%" + value + "%"); - - switch (key) { - case UserModel.SEARCH: - DefaultModelCriteria searchCriteria = null; - for (String stringToSearch : value.split("\\s+")) { - if (searchCriteria == null) { - searchCriteria = addSearchToModelCriteria(realm, stringToSearch, mcb); - } else { - searchCriteria = mcb.and(searchCriteria, addSearchToModelCriteria(realm, stringToSearch, mcb)); - } - } - - criteria = mcb.and(criteria, searchCriteria); - break; - case USERNAME: - criteria = isUsernameCaseSensitive(realm) ? - criteria.compare(SearchableFields.USERNAME, Operator.LIKE, searchedString) : - criteria.compare(SearchableFields.USERNAME_CASE_INSENSITIVE, Operator.ILIKE, searchedString); - break; - case FIRST_NAME: - criteria = criteria.compare(SearchableFields.FIRST_NAME, Operator.ILIKE, searchedString); - break; - case LAST_NAME: - criteria = criteria.compare(SearchableFields.LAST_NAME, Operator.ILIKE, searchedString); - break; - case EMAIL: - criteria = criteria.compare(SearchableFields.EMAIL, Operator.ILIKE, searchedString); - break; - case EMAIL_VERIFIED: { - boolean booleanValue = Boolean.parseBoolean(value); - criteria = criteria.compare(SearchableFields.EMAIL_VERIFIED, Operator.EQ, booleanValue); - break; - } - case UserModel.ENABLED: { - boolean booleanValue = Boolean.parseBoolean(value); - criteria = criteria.compare(SearchableFields.ENABLED, Operator.EQ, booleanValue); - break; - } - case UserModel.IDP_ALIAS: { - if (!attributes.containsKey(UserModel.IDP_USER_ID)) { - criteria = criteria.compare(SearchableFields.IDP_AND_USER, Operator.EQ, value); - } - break; - } - case UserModel.IDP_USER_ID: { - criteria = criteria.compare(SearchableFields.IDP_AND_USER, Operator.EQ, attributes.get(UserModel.IDP_ALIAS), - value); - break; - } - case UserModel.INCLUDE_SERVICE_ACCOUNT: { - if (!attributes.containsKey(UserModel.INCLUDE_SERVICE_ACCOUNT) - || !Boolean.parseBoolean(attributes.get(UserModel.INCLUDE_SERVICE_ACCOUNT))) { - criteria = criteria.compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.NOT_EXISTS); - } - break; - } - case UserModel.EXACT: - break; - default: - criteria = criteria.compare(SearchableFields.ATTRIBUTE, Operator.EQ, key, value); - break; - } - } - return criteria; - } - - @Override - public int getUsersCount(RealmModel realm, Map attributes) { - LOG.tracef("getUsersCount(%s, %s)%s", realm, attributes, getShortStackTrace()); - - final DefaultModelCriteria mcb = criteria(); - DefaultModelCriteria criteria = resolveCriteria(realm, attributes, mcb); - - return (int) storeWithRealm(realm).getCount(withCriteria(criteria)); - } - - @Override - public Stream searchForUserStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) { - LOG.tracef("searchForUserStream(%s, %s, %d, %d)%s", realm, search, firstResult, maxResults, getShortStackTrace()); - Map attributes = new HashMap<>(); - attributes.put(UserModel.SEARCH, search); - attributes.put(UserModel.INCLUDE_SERVICE_ACCOUNT, Boolean.FALSE.toString()); - return searchForUserStream(realm, attributes, firstResult, maxResults); - } - - @Override - public Stream searchForUserStream(RealmModel realm, Map attributes, Integer firstResult, Integer maxResults) { - LOG.tracef("searchForUserStream(%s, %s, %d, %d)%s", realm, attributes, firstResult, maxResults, getShortStackTrace()); - - final DefaultModelCriteria mcb = criteria(); - DefaultModelCriteria criteria = resolveCriteria(realm, attributes, mcb); - - // Only return those results that the current user is authorized to view, - // i.e. there is an intersection of groups with view permission of the current - // user (passed in via UserModel.GROUPS attribute), the groups for the returned - // users, and the respective group resource available from the authorization provider - @SuppressWarnings("unchecked") - Set userGroups = (Set) session.getAttribute(UserModel.GROUPS); - if (userGroups != null) { - if (userGroups.isEmpty()) { - return Stream.empty(); - } - - final ResourceStore resourceStore = - session.getProvider(AuthorizationProvider.class).getStoreFactory().getResourceStore(); - - HashSet authorizedGroups = new HashSet<>(userGroups); - authorizedGroups.removeIf(id -> { - Map values = new EnumMap<>(Resource.FilterOption.class); - values.put(Resource.FilterOption.EXACT_NAME, new String[] {"group.resource." + id}); - return resourceStore.find(realm, null, values, 0, 1).isEmpty(); - }); - - criteria = criteria.compare(SearchableFields.ASSIGNED_GROUP, Operator.IN, authorizedGroups); - } - - return storeWithRealm(realm).read(withCriteria(criteria).pagination(firstResult, maxResults, SearchableFields.USERNAME)) - .map(entityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public Stream getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) { - LOG.tracef("getGroupMembersStream(%s, %s, %d, %d)%s", realm, group.getId(), firstResult, maxResults, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ASSIGNED_GROUP, Operator.EQ, group.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public Stream searchForUserByUserAttributeStream(RealmModel realm, String attrName, String attrValue) { - LOG.tracef("searchForUserByUserAttributeStream(%s, %s, %s)%s", realm, attrName, attrValue, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ATTRIBUTE, Operator.EQ, attrName, attrValue); - - return storeWithRealm(realm).read(withCriteria(mcb).orderBy(SearchableFields.USERNAME, ASCENDING)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public UserModel addUser(RealmModel realm, String username) { - return addUser(realm, null, username, true, true); - } - - @Override - public boolean removeUser(RealmModel realm, UserModel user) { - LOG.tracef("removeUser(%s, %s)%s", realm, user, getShortStackTrace()); - String userId = user.getId(); - Optional userById = getEntityById(realm, userId); - if (userById.isPresent()) { - session.invalidate(USER_BEFORE_REMOVE, realm, user); - - storeWithRealm(realm).delete(userId); - - session.invalidate(USER_AFTER_REMOVE, realm, user); - return true; - } - - return false; - } - - @Override - public Stream getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) { - LOG.tracef("getRoleMembersStream(%s, %s, %d, %d)%s", realm, role, firstResult, maxResults, getShortStackTrace()); - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME)) - .map(entityToAdapterFunc(realm)); - } - - @Override - public void close() { - - } - - public static Stream getCredentialProviders(KeycloakSession session, Class type) { - return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class) - .filter(f -> Types.supports(type, f, CredentialProviderFactory.class)) - .map(f -> (T) session.getProvider(CredentialProvider.class, f.getId())); - } - - @Override - public CredentialValidationOutput getUserByCredential(RealmModel realm, CredentialInput input) { - // TODO: future implementations would narrow down the stream to those provider enabled for the specific realm - Stream credentialAuthenticationStream = getCredentialProviders(session, CredentialAuthentication.class); - - CredentialValidationOutput r = credentialAuthenticationStream - .filter(credentialAuthentication -> credentialAuthentication.supportsCredentialAuthenticationFor(input.getType())) - .map(credentialAuthentication -> credentialAuthentication.authenticate(realm, input)) - .filter(Objects::nonNull) - .findFirst().orElse(null); - - if (r == null && store instanceof MapStorageWithAuth) { - MapCredentialValidationOutput result = ((MapStorageWithAuth) store).authenticate(realm, input); - if (result != null) { - UserModel user = null; - if (result.getAuthenticatedUser() != null) { - user = entityToAdapterFunc(realm).apply(result.getAuthenticatedUser()); - } - r = new CredentialValidationOutput(user, result.getAuthStatus(), result.getState()); - } - } - return r; - } - - @SuppressWarnings("unchecked") - private DefaultModelCriteria addSearchToModelCriteria(RealmModel realm, String value, - DefaultModelCriteria mcb) { - - if (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') { - // exact search - value = value.substring(1, value.length() - 1); - } else { - value = value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_"); - value = value.replace("*", "%"); - if (value.isEmpty() || value.charAt(value.length() - 1) != '%') value += "%"; - } - - return mcb.or( - isUsernameCaseSensitive(realm) ? - mcb.compare(SearchableFields.USERNAME, Operator.LIKE, value) : - mcb.compare(SearchableFields.USERNAME_CASE_INSENSITIVE, Operator.ILIKE, value), - mcb.compare(SearchableFields.EMAIL, Operator.ILIKE, value), - mcb.compare(SearchableFields.FIRST_NAME, Operator.ILIKE, value), - mcb.compare(SearchableFields.LAST_NAME, Operator.ILIKE, value)); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java deleted file mode 100644 index 4fb85140e1d..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java +++ /dev/null @@ -1,71 +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.user; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserProviderFactory; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE; - -/** - * - * @author mhajas - */ -public class MapUserProviderFactory extends AbstractMapProviderFactory implements UserProviderFactory, InvalidationHandler { - - public MapUserProviderFactory() { - super(UserModel.class, MapUserProvider.class); - } - - @Override - public MapUserProvider createNew(KeycloakSession session) { - return new MapUserProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "User provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == REALM_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0]); - } else if (type == ROLE_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]); - } else if (type == CLIENT_SCOPE_BEFORE_REMOVE) { - create(session).preRemove((ClientScopeModel) params[1]); - } else if (type == CLIENT_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0], (ClientModel) params[1]); - } else if (type == GROUP_BEFORE_REMOVE) { - create(session).preRemove((RealmModel) params[0], (GroupModel) params[1]); - } - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/AbstractAuthenticatedClientSessionModel.java b/model/map/src/main/java/org/keycloak/models/map/userSession/AbstractAuthenticatedClientSessionModel.java deleted file mode 100644 index cc9dc1d0d3a..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/AbstractAuthenticatedClientSessionModel.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.AbstractEntity; - -import java.util.Objects; - -/** - * @author Martin Kanis - */ -public abstract class AbstractAuthenticatedClientSessionModel implements AuthenticatedClientSessionModel { - protected final KeycloakSession session; - protected final RealmModel realm; - protected ClientModel client; - protected UserSessionModel userSession; - protected final MapAuthenticatedClientSessionEntity entity; - - public AbstractAuthenticatedClientSessionModel(KeycloakSession session, RealmModel realm, - UserSessionModel userSession, MapAuthenticatedClientSessionEntity entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - Objects.requireNonNull(userSession, "userSession"); - - this.session = session; - this.realm = realm; - this.userSession = userSession; - this.entity = entity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof AuthenticatedClientSessionModel)) return false; - - AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/AbstractUserSessionModel.java b/model/map/src/main/java/org/keycloak/models/map/userSession/AbstractUserSessionModel.java deleted file mode 100644 index d681d312597..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/AbstractUserSessionModel.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserSessionModel; - -import java.util.Objects; - -/** - * @author Martin Kanis - */ -public abstract class AbstractUserSessionModel implements UserSessionModel { - protected final KeycloakSession session; - protected final RealmModel realm; - protected final MapUserSessionEntity entity; - - public AbstractUserSessionModel(KeycloakSession session, RealmModel realm, MapUserSessionEntity entity) { - Objects.requireNonNull(entity, "entity"); - Objects.requireNonNull(realm, "realm"); - - this.session = session; - this.realm = realm; - this.entity = entity; - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapAuthenticatedClientSessionAdapter.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapAuthenticatedClientSessionAdapter.java deleted file mode 100644 index ef437a9010f..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapAuthenticatedClientSessionAdapter.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.TimeAdapter; - -import java.util.Collections; -import java.util.Map; - -import static org.keycloak.models.map.userSession.SessionExpiration.setClientSessionExpiration; - -/** - * @author Martin Kanis - */ -public abstract class MapAuthenticatedClientSessionAdapter extends AbstractAuthenticatedClientSessionModel { - - public MapAuthenticatedClientSessionAdapter(KeycloakSession session, RealmModel realm, - UserSessionModel userSession, MapAuthenticatedClientSessionEntity entity) { - super(session, realm, userSession, entity); - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public int getTimestamp() { - Long timestamp = entity.getTimestamp(); - return timestamp != null ? TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(TimeAdapter.fromMilliSecondsToSeconds(timestamp)) : 0; - } - - @Override - public void setTimestamp(int timestamp) { - entity.setTimestamp(TimeAdapter.fromSecondsToMilliseconds(timestamp)); - - // whenever the timestamp is changed recompute the expiration time - setClientSessionExpiration(entity, realm, getClient()); - } - - @Override - public UserSessionModel getUserSession() { - return userSession; - } - - @Override - public String getCurrentRefreshToken() { - return entity.getCurrentRefreshToken(); - } - - @Override - public void setCurrentRefreshToken(String currentRefreshToken) { - entity.setCurrentRefreshToken(currentRefreshToken); - } - - @Override - public int getCurrentRefreshTokenUseCount() { - Integer currentRefreshTokenUseCount = entity.getCurrentRefreshTokenUseCount(); - return currentRefreshTokenUseCount != null ? currentRefreshTokenUseCount : 0; - } - - @Override - public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) { - entity.setCurrentRefreshTokenUseCount(currentRefreshTokenUseCount); - } - - @Override - public String getNote(String name) { - return (name != null) ? entity.getNote(name) : null; - } - - @Override - public void setNote(String name, String value) { - if (name != null) { - if (value == null) { - entity.removeNote(name); - } else { - entity.setNote(name, value); - } - } - } - - @Override - public void removeNote(String name) { - if (name != null) { - entity.removeNote(name); - } - } - - @Override - public Map getNotes() { - Map notes = entity.getNotes(); - return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); - } - - @Override - public String getRedirectUri() { - return entity.getRedirectUri(); - } - - @Override - public void setRedirectUri(String uri) { - entity.setRedirectUri(uri); - } - - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public ClientModel getClient() { - return realm.getClientById(entity.getClientId()); - } - - @Override - public String getAction() { - return entity.getAction(); - } - - @Override - public void setAction(String action) { - entity.setAction(action); - } - - @Override - public String getProtocol() { - return entity.getAuthMethod(); - } - - @Override - public void setProtocol(String method) { - entity.setAuthMethod(method); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), hashCode()); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapAuthenticatedClientSessionEntity.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapAuthenticatedClientSessionEntity.java deleted file mode 100644 index 1a2688ba4ba..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapAuthenticatedClientSessionEntity.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.models.map.annotations.CollectionKey; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.common.AbstractEntity; - -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.UpdatableEntity; -import java.util.Map; - -/** - * @author Martin Kanis - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity.AbstractAuthenticatedClientSessionEntity" -) -@DeepCloner.Root -public interface MapAuthenticatedClientSessionEntity extends AbstractEntity, UpdatableEntity, ExpirableEntity { - - abstract class AbstractAuthenticatedClientSessionEntity extends UpdatableEntity.Impl implements MapAuthenticatedClientSessionEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - } - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the client session entity was created or updated (refreshed). - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the client session entity was created or updated (refreshed). - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - String getRealmId(); - void setRealmId(String realmId); - - @CollectionKey(priority = 1) - String getClientId(); - void setClientId(String clientId); - - String getAuthMethod(); - void setAuthMethod(String authMethod); - - String getRedirectUri(); - void setRedirectUri(String redirectUri); - String getAction(); - void setAction(String action); - - Map getNotes(); - void setNotes(Map notes); - String getNote(String name); - Boolean removeNote(String name); - void setNote(String name, String value); - - String getCurrentRefreshToken(); - void setCurrentRefreshToken(String currentRefreshToken); - - Integer getCurrentRefreshTokenUseCount(); - void setCurrentRefreshTokenUseCount(Integer currentRefreshTokenUseCount); - - Boolean isOffline(); - void setOffline(Boolean offline); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java deleted file mode 100644 index 5087347208b..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.common.util.Time; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelIllegalStateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.common.TimeAdapter; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.models.map.userSession.SessionExpiration.setUserSessionExpiration; - -/** - * @author Martin Kanis - */ -public class MapUserSessionAdapter extends AbstractUserSessionModel { - - private final UserModel user; - - public MapUserSessionAdapter(KeycloakSession session, RealmModel realm, UserModel userModel, MapUserSessionEntity entity) { - super(session, realm, entity); - this.user = userModel; - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public String getBrokerSessionId() { - return entity.getBrokerSessionId(); - } - - @Override - public String getBrokerUserId() { - return entity.getBrokerUserId(); - } - - @Override - public UserModel getUser() { - return this.user; - } - - @Override - public String getLoginUsername() { - return entity.getLoginUsername(); - } - - @Override - public String getIpAddress() { - return entity.getIpAddress(); - } - - @Override - public String getAuthMethod() { - return entity.getAuthMethod(); - } - - @Override - public boolean isRememberMe() { - Boolean rememberMe = entity.isRememberMe(); - return rememberMe != null ? rememberMe : false; - } - - @Override - public int getStarted() { - Long started = entity.getTimestamp(); - return started != null ? TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(TimeAdapter.fromMilliSecondsToSeconds(started)) : 0; - } - - @Override - public int getLastSessionRefresh() { - Long lastSessionRefresh = entity.getLastSessionRefresh(); - return lastSessionRefresh != null ? TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(TimeAdapter.fromMilliSecondsToSeconds(lastSessionRefresh)) : 0; - } - - @Override - public void setLastSessionRefresh(int seconds) { - entity.setLastSessionRefresh(TimeAdapter.fromSecondsToMilliseconds(seconds)); - - // whenever the lastSessionRefresh is changed recompute the expiration time - setUserSessionExpiration(entity, realm); - } - - @Override - public boolean isOffline() { - Boolean offline = entity.isOffline(); - return offline != null ? offline : false; - } - - @Override - public Map getAuthenticatedClientSessions() { - Set authenticatedClientSessions = entity.getAuthenticatedClientSessions(); - if (authenticatedClientSessions == null) { - return Collections.emptyMap(); - } - - return authenticatedClientSessions - .stream() - .filter(this::filterAndRemoveExpiredClientSessions) - .filter(this::matchingOfflineFlag) - .filter(this::filterAndRemoveClientSessionWithoutClient) - .collect(Collectors.toMap(MapAuthenticatedClientSessionEntity::getClientId, this::clientSessionEntityToModel)); - } - - private AuthenticatedClientSessionModel clientSessionEntityToModel(MapAuthenticatedClientSessionEntity clientSessionEntity) { - return new MapAuthenticatedClientSessionAdapter(session, realm, this, clientSessionEntity) { - @Override - public void detachFromUserSession() { - MapUserSessionAdapter.this.entity.removeAuthenticatedClientSession(entity.getClientId()); - this.userSession = null; - } - }; - } - - public boolean filterAndRemoveExpiredClientSessions(MapAuthenticatedClientSessionEntity clientSession) { - try { - if (isExpired(clientSession, false)) { - entity.removeAuthenticatedClientSession(clientSession.getClientId()); - return false; - } - } catch (ModelIllegalStateException ex) { - entity.removeAuthenticatedClientSession(clientSession.getClientId()); - return false; - } - - return true; - } - - public boolean filterAndRemoveClientSessionWithoutClient(MapAuthenticatedClientSessionEntity clientSession) { - ClientModel client = realm.getClientById(clientSession.getClientId()); - - if (client == null) { - entity.removeAuthenticatedClientSession(clientSession.getId()); - - // Filter out entities that doesn't have client - return false; - } - - // client session has client so we do not filter it out - return true; - } - - public boolean matchingOfflineFlag(MapAuthenticatedClientSessionEntity clientSession) { - Boolean isClientSessionOffline = clientSession.isOffline(); - - // If client session doesn't have offline flag default to false - if (isClientSessionOffline == null) { - return !isOffline(); - } - - return isOffline() == isClientSessionOffline; - } - - @Override - public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) { - return entity.getAuthenticatedClientSession(clientUUID) - .filter(this::filterAndRemoveExpiredClientSessions) - .filter(this::matchingOfflineFlag) - .filter(this::filterAndRemoveClientSessionWithoutClient) - .map(this::clientSessionEntityToModel) - .orElse(null); - } - - @Override - public void removeAuthenticatedClientSessions(Collection removedClientUKS) { - removedClientUKS.forEach(entity::removeAuthenticatedClientSession); - } - - @Override - public String getNote(String name) { - return (name != null) ? entity.getNote(name) : null; - } - - @Override - public void setNote(String name, String value) { - if (name != null) { - if (value == null) { - entity.removeNote(name); - } else { - entity.setNote(name, value); - } - } - } - - @Override - public void removeNote(String name) { - if (name != null) { - entity.removeNote(name); - } - } - - @Override - public Map getNotes() { - Map notes = entity.getNotes(); - return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); - } - - @Override - public State getState() { - return entity.getState(); - } - - @Override - public void setState(State state) { - entity.setState(state); - } - - @Override - public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, - boolean rememberMe, String brokerSessionId, String brokerUserId) { - entity.setRealmId(realm.getId()); - entity.setUserId(user.getId()); - entity.setLoginUsername(loginUsername); - entity.setIpAddress(ipAddress); - entity.setAuthMethod(authMethod); - entity.setRememberMe(rememberMe); - entity.setBrokerSessionId(brokerSessionId); - entity.setBrokerUserId(brokerUserId); - - long currentTime = Time.currentTimeMillis(); - entity.setTimestamp(currentTime); - entity.setLastSessionRefresh(currentTime); - - entity.setState(null); - - String correspondingSessionId = entity.getNote(CORRESPONDING_SESSION_ID); - entity.setNotes(new ConcurrentHashMap<>()); - if (correspondingSessionId != null) { - entity.setNote(CORRESPONDING_SESSION_ID, correspondingSessionId); - } - - entity.clearAuthenticatedClientSessions(); - } - - @Override - public String toString() { - return String.format("%s@%08x", getId(), hashCode()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof UserSessionModel)) { - return false; - } - - UserSessionModel that = (UserSessionModel) o; - return Objects.equals(that.getId(), getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public SessionPersistenceState getPersistenceState() { - return entity.getPersistenceState(); - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionEntity.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionEntity.java deleted file mode 100644 index 4244102aa1e..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionEntity.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.map.annotations.GenerateEntityImplementations; -import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator; -import org.keycloak.models.map.client.MapProtocolMapperEntity; -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.ExpirableEntity; -import org.keycloak.models.map.common.UpdatableEntity; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * @author Martin Kanis - */ -@GenerateEntityImplementations( - inherits = "org.keycloak.models.map.userSession.MapUserSessionEntity.AbstractUserSessionEntity" -) -@DeepCloner.Root -public interface MapUserSessionEntity extends AbstractEntity, UpdatableEntity, ExpirableEntity { - - abstract class AbstractUserSessionEntity extends UpdatableEntity.Impl implements MapUserSessionEntity { - - private String id; - - @Override - public String getId() { - return this.id; - } - - @Override - public void setId(String id) { - if (this.id != null) throw new IllegalStateException("Id cannot be changed"); - this.id = id; - this.updated |= id != null; - } - - @Override - public boolean isUpdated() { - return this.updated - || Optional.ofNullable(getAuthenticatedClientSessions()).orElseGet(Collections::emptySet).stream().anyMatch(UpdatableEntity::isUpdated); - } - - @Override - public void clearUpdatedFlag() { - this.updated = false; - Optional.ofNullable(getAuthenticatedClientSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag); - } - - @Override - public void clearAuthenticatedClientSessions() { - Set acss = getAuthenticatedClientSessions(); - if (acss != null) { - acss.stream().map(MapAuthenticatedClientSessionEntity::getClientId) - .collect(Collectors.toSet()) - .forEach(this::removeAuthenticatedClientSession); - } - } - } - - String getRealmId(); - void setRealmId(String realmId); - - String getUserId(); - void setUserId(String userId); - - String getBrokerSessionId(); - void setBrokerSessionId(String brokerSessionId); - - String getBrokerUserId(); - void setBrokerUserId(String brokerUserId); - - String getLoginUsername(); - void setLoginUsername(String loginUsername); - - String getIpAddress(); - void setIpAddress(String ipAddress); - - String getAuthMethod(); - void setAuthMethod(String authMethod); - - Boolean isRememberMe(); - void setRememberMe(Boolean rememberMe); - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the user session entity was created. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getTimestamp(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the user session entity was created. - * @param timestamp a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setTimestamp(Long timestamp); - - /** - * Returns a point in time (timestamp in milliseconds since The Epoch) when the user session entity was last refreshed. - * - * @return a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - Long getLastSessionRefresh(); - - /** - * Sets a point in the (timestamp in milliseconds since The Epoch) when the user session entity was last refreshed. - * @param lastSessionRefresh a timestamp in milliseconds since The Epoch or {@code null} when the time is unknown - */ - void setLastSessionRefresh(Long lastSessionRefresh); - - Map getNotes(); - String getNote(String name); - void setNotes(Map notes); - Boolean removeNote(String name); - void setNote(String name, String value); - - UserSessionModel.State getState(); - void setState(UserSessionModel.State state); - - Set getAuthenticatedClientSessions(); - Optional getAuthenticatedClientSession(String clientUUID); - void addAuthenticatedClientSession(MapAuthenticatedClientSessionEntity clientSession); - Boolean removeAuthenticatedClientSession(String clientUUID); - @IgnoreForEntityImplementationGenerator - void clearAuthenticatedClientSessions(); - - Boolean isOffline(); - void setOffline(Boolean offline); - - UserSessionModel.SessionPersistenceState getPersistenceState(); - void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState); -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java deleted file mode 100644 index 5c1588d5bc2..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java +++ /dev/null @@ -1,675 +0,0 @@ -/* - * 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.userSession; - -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.HasRealmId; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.keycloak.common.util.StackUtil.getShortStackTrace; -import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID; -import static org.keycloak.models.UserSessionModel.SessionPersistenceState.TRANSIENT; -import static org.keycloak.models.map.common.ExpirationUtils.isExpired; -import static org.keycloak.models.map.storage.QueryParameters.withCriteria; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; -import static org.keycloak.models.map.userSession.SessionExpiration.setClientSessionExpiration; -import static org.keycloak.models.map.userSession.SessionExpiration.setUserSessionExpiration; - -/** - * @author Martin Kanis - */ -public class MapUserSessionProvider implements UserSessionProvider { - - private static final Logger LOG = Logger.getLogger(MapUserSessionProvider.class); - private final KeycloakSession session; - protected final MapStorage userSessionTx; - - /** - * Storage for transient user sessions which lifespan is limited to one request. - */ - private final Map transientUserSessions = new HashMap<>(); - private final boolean storeHasRealmId; - - public MapUserSessionProvider(KeycloakSession session, MapStorage userSessionStore) { - this.session = session; - this.userSessionTx = userSessionStore; - this.storeHasRealmId = userSessionTx instanceof HasRealmId; - } - - private Function userEntityToAdapterFunc(RealmModel realm) { - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return (origEntity) -> { - if (origEntity == null) return null; - if (isExpired(origEntity, false)) { - if (TRANSIENT == origEntity.getPersistenceState()) { - transientUserSessions.remove(origEntity.getId()); - } else { - storeWithRealm(realm).delete(origEntity.getId()); - } - return null; - } else { - UserModel userModel = session.users().getUserById(realm, origEntity.getUserId()); - if (userModel != null) { - return new MapUserSessionAdapter(session, realm, userModel, origEntity); - } - return null; - } - }; - } - - private MapStorage storeWithRealm(RealmModel realm) { - if (storeHasRealmId) { - ((HasRealmId) userSessionTx).setRealmId(realm == null ? null : realm.getId()); - } - return userSessionTx; - } - - @Override - public KeycloakSession getKeycloakSession() { - return session; - } - - @Override - public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) { - LOG.tracef("createClientSession(%s, %s, %s)%s", realm, client, userSession, getShortStackTrace()); - - MapUserSessionEntity userSessionEntity = getUserSessionById(realm, userSession.getId()); - - if (userSessionEntity == null) { - throw new IllegalStateException("User session entity does not exist: " + userSession.getId()); - } - - if (userSessionEntity.getAuthenticatedClientSession(client.getId()).isPresent()) { - userSessionEntity.removeAuthenticatedClientSession(client.getId()); - } - - MapAuthenticatedClientSessionEntity entity = createAuthenticatedClientSessionEntityInstance(null, userSession.getId(), - realm.getId(), client.getId(), userSession.isOffline()); - String started = entity.getTimestamp() != null ? String.valueOf(TimeAdapter.fromMilliSecondsToSeconds(entity.getTimestamp())) : String.valueOf(0); - entity.setNote(AuthenticatedClientSessionModel.STARTED_AT_NOTE, started); - entity.setNote(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(userSession.getStarted())); - if (userSession.isRememberMe()) { - entity.setNote(AuthenticatedClientSessionModel.USER_SESSION_REMEMBER_ME_NOTE, "true"); - } - setClientSessionExpiration(entity, realm, client); - userSessionEntity.addAuthenticatedClientSession(entity); - - // We need to load the clientSession through userModel so we return an entity that is included within the - // transaction and also, so we not avoid all the checks present in the adapter, for example expiration - UserSessionModel userSessionModel = userEntityToAdapterFunc(realm).apply(userSessionEntity); - return userSessionModel == null ? null : userSessionModel.getAuthenticatedClientSessionByClient(client.getId()); - } - - @Override - public AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, - String clientSessionId, boolean offline) { - LOG.tracef("getClientSession(%s, %s, %s, %s)%s", userSession, client, - clientSessionId, offline, getShortStackTrace()); - - return userSession.getAuthenticatedClientSessionByClient(client.getId()); - } - - @Override - public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, - String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, - String brokerUserId, UserSessionModel.SessionPersistenceState persistenceState) { - LOG.tracef("createUserSession(%s, %s, %s, %s)%s", id, realm, loginUsername, persistenceState, getShortStackTrace()); - - MapUserSessionEntity entity = createUserSessionEntityInstance(id, realm.getId(), user.getId(), loginUsername, ipAddress, authMethod, - rememberMe, brokerSessionId, brokerUserId, false); - - if (TRANSIENT == persistenceState) { - if (id == null) { - entity.setId(UUID.randomUUID().toString()); - } - transientUserSessions.put(entity.getId(), entity); - } else { - if (id != null && storeWithRealm(realm).exists(id)) { - throw new ModelDuplicateException("User session exists: " + id); - } - entity = storeWithRealm(realm).create(entity); - } - - entity.setPersistenceState(persistenceState); - setUserSessionExpiration(entity, realm); - UserSessionModel userSession = userEntityToAdapterFunc(realm).apply(entity); - - return userSession; - } - - @Override - public UserSessionModel getUserSession(RealmModel realm, String id) { - Objects.requireNonNull(realm, "The provided realm can't be null!"); - - LOG.tracef("getUserSession(%s, %s)%s", realm, id, getShortStackTrace()); - - if (id == null) return null; - - MapUserSessionEntity userSessionEntity = transientUserSessions.get(id); - if (userSessionEntity != null) { - return userEntityToAdapterFunc(realm).apply(userSessionEntity); - } - - // This is an exceptional case where not to use the criteria query: - // As the ID is already known, and we expect in almost all cases to have exactly one row being returned, - // the provider fetches the instance by ID and does the filtering in the Java code afterward instead - // of using the criteria query. When using a criteria query in earlier versions, the store (CockroachDB) would pick - // a wrong optimization path and lock too many DB rows which would result in transaction-not-serializable exceptions - // on concurrent transactions. This change has been done in the assumption that all stores would be faster - // to evaluate the fetch-by-id than a criteria query. - userSessionEntity = storeWithRealm(realm).read(id); - if (userSessionEntity != null && Objects.equals(userSessionEntity.getRealmId(), realm.getId()) && !userSessionEntity.isOffline()) { - return userEntityToAdapterFunc(realm).apply(userSessionEntity); - } - - return null; - } - - @Override - public Stream getUserSessionsStream(RealmModel realm, UserModel user) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.USER_ID, Operator.EQ, user.getId()); - - LOG.tracef("getUserSessionsStream(%s, %s)%s", realm, user, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public Stream getUserSessionsStream(RealmModel realm, ClientModel client) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - LOG.tracef("getUserSessionsStream(%s, %s)%s", realm, client, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public Stream getUserSessionsStream(RealmModel realm, ClientModel client, - Integer firstResult, Integer maxResults) { - LOG.tracef("getUserSessionsStream(%s, %s, %s, %s)%s", realm, client, firstResult, maxResults, getShortStackTrace()); - - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, - UserSessionModel.SearchableFields.LAST_SESSION_REFRESH)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public Stream getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.BROKER_USER_ID, Operator.EQ, brokerUserId); - - LOG.tracef("getUserSessionByBrokerUserIdStream(%s, %s)%s", realm, brokerUserId, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.BROKER_SESSION_ID, Operator.EQ, brokerSessionId); - - LOG.tracef("getUserSessionByBrokerSessionId(%s, %s)%s", realm, brokerSessionId, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .findFirst() - .map(userEntityToAdapterFunc(realm)) - .orElse(null); - } - - @Override - public UserSessionModel getUserSessionWithPredicate(RealmModel realm, String id, boolean offline, - Predicate predicate) { - LOG.tracef("getUserSessionWithPredicate(%s, %s, %s)%s", realm, id, offline, getShortStackTrace()); - - Stream userSessionEntityStream; - if (offline) { - userSessionEntityStream = getOfflineUserSessionEntityStream(realm, id) - .map(userEntityToAdapterFunc(realm)).filter(Objects::nonNull); - } else { - UserSessionModel userSession = getUserSession(realm, id); - userSessionEntityStream = userSession != null ? Stream.of(userSession) : Stream.empty(); - } - - return userSessionEntityStream - .filter(predicate) - .findFirst() - .orElse(null); - } - - @Override - public long getActiveUserSessions(RealmModel realm, ClientModel client) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - LOG.tracef("getActiveUserSessions(%s, %s)%s", realm, client, getShortStackTrace()); - - return storeWithRealm(realm).getCount(withCriteria(mcb)); - } - - @Override - public Map getActiveClientSessionStats(RealmModel realm, boolean offline) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, offline); - - LOG.tracef("getActiveClientSessionStats(%s, %s)%s", realm, offline, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull) - .map(UserSessionModel::getAuthenticatedClientSessions) - .map(Map::keySet) - .flatMap(Collection::stream) - .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); - } - - @Override - public void removeUserSession(RealmModel realm, UserSessionModel session) { - Objects.requireNonNull(session, "The provided user session can't be null!"); - - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false) - .compare(UserSessionModel.SearchableFields.ID, Operator.EQ, session.getId()); - - LOG.tracef("removeUserSession(%s, %s)%s", realm, session, getShortStackTrace()); - - // This is an exceptional case where not to use the criteria query: - // As the ID is already known, the provider does the filtering in the Java code and uses delete-by-id - // instead of using the criteria query to delete rows. - // When using a criteria query in earlier versions, the store (CockroachDB) would pick - // a wrong optimization path and lock too many DB rows which would result in transaction-not-serializable exceptions - // on concurrent transactions. This change has been done in the assumption that delete-by-id would be faster than - // delete-by-criteria for all stores. - if (Objects.equals(session.getRealm(), realm) && !session.isOffline()) { - storeWithRealm(realm).delete(session.getId()); - } - } - - @Override - public void removeUserSessions(RealmModel realm, UserModel user) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserSessionModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(UserSessionModel.SearchableFields.USER_ID, Operator.EQ, user.getId()); - - LOG.tracef("removeUserSessions(%s, %s)%s", realm, user, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void removeAllExpired() { - LOG.tracef("removeAllExpired()%s", getShortStackTrace()); - } - - @Override - public void removeExpired(RealmModel realm) { - LOG.tracef("removeExpired(%s)%s", realm, getShortStackTrace()); - } - - @Override - public void removeUserSessions(RealmModel realm) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, false); - - LOG.tracef("removeUserSessions(%s)%s", realm, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - @Override - public void onRealmRemoved(RealmModel realm) { - LOG.tracef("onRealmRemoved(%s)%s", realm, getShortStackTrace()); - } - - @Override - public void onClientRemoved(RealmModel realm, ClientModel client) { - - } - - @Override - public UserSessionModel createOfflineUserSession(UserSessionModel userSession) { - LOG.tracef("createOfflineUserSession(%s)%s", userSession, getShortStackTrace()); - - MapUserSessionEntity offlineUserSession = createUserSessionEntityInstance(userSession, true); - RealmModel realm = userSession.getRealm(); - offlineUserSession = storeWithRealm(realm).create(offlineUserSession); - - // set a reference for the offline user session to the original online user session - userSession.setNote(CORRESPONDING_SESSION_ID, offlineUserSession.getId()); - - long currentTime = Time.currentTimeMillis(); - offlineUserSession.setTimestamp(currentTime); - offlineUserSession.setLastSessionRefresh(currentTime); - setUserSessionExpiration(offlineUserSession, userSession.getRealm()); - - return userEntityToAdapterFunc(userSession.getRealm()).apply(offlineUserSession); - } - - @Override - public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) { - LOG.tracef("getOfflineUserSession(%s, %s)%s", realm, userSessionId, getShortStackTrace()); - - return getOfflineUserSessionEntityStream(realm, userSessionId) - .findFirst() - .map(userEntityToAdapterFunc(realm)) - .orElse(null); - } - - @Override - public void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession) { - Objects.requireNonNull(userSession, "The provided user session can't be null!"); - - LOG.tracef("removeOfflineUserSession(%s, %s)%s", realm, userSession, getShortStackTrace()); - - DefaultModelCriteria mcb; - if (userSession.isOffline()) { - storeWithRealm(realm).delete(userSession.getId()); - } else if (userSession.getNote(CORRESPONDING_SESSION_ID) != null) { - String uk = userSession.getNote(CORRESPONDING_SESSION_ID); - mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.ID, Operator.EQ, uk); - storeWithRealm(realm).delete(withCriteria(mcb)); - userSession.removeNote(CORRESPONDING_SESSION_ID); - } - } - - @Override - public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, - UserSessionModel offlineUserSession) { - LOG.tracef("createOfflineClientSession(%s, %s)%s", clientSession, offlineUserSession, getShortStackTrace()); - - MapAuthenticatedClientSessionEntity clientSessionEntity = createAuthenticatedClientSessionInstance(clientSession, offlineUserSession, true); - int currentTime = Time.currentTime(); - clientSessionEntity.setNote(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(currentTime)); - clientSessionEntity.setNote(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(offlineUserSession.getStarted())); - clientSessionEntity.setTimestamp(Time.currentTimeMillis()); - RealmModel realm = clientSession.getRealm(); - setClientSessionExpiration(clientSessionEntity, realm, clientSession.getClient()); - - Optional userSessionEntity = getOfflineUserSessionEntityStream(realm, offlineUserSession.getId()).findFirst(); - if (userSessionEntity.isPresent()) { - MapUserSessionEntity userSession = userSessionEntity.get(); - String clientId = clientSession.getClient().getId(); - if (userSession.getAuthenticatedClientSession(clientId).isPresent()) { - userSession.removeAuthenticatedClientSession(clientId); - } - - userSession.addAuthenticatedClientSession(clientSessionEntity); - - UserSessionModel userSessionModel = userEntityToAdapterFunc(realm).apply(userSession); - return userSessionModel == null ? null : userSessionModel.getAuthenticatedClientSessionByClient(clientId); - } - - return null; - } - - @Override - public Stream getOfflineUserSessionsStream(RealmModel realm, UserModel user) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.USER_ID, Operator.EQ, user.getId()); - - LOG.tracef("getOfflineUserSessionsStream(%s, %s)%s", realm, user, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.BROKER_SESSION_ID, Operator.EQ, brokerSessionId); - - LOG.tracef("getOfflineUserSessionByBrokerSessionId(%s, %s)%s", realm, brokerSessionId, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .findFirst() - .map(userEntityToAdapterFunc(realm)) - .orElse(null); - } - - @Override - public Stream getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.BROKER_USER_ID, Operator.EQ, brokerUserId); - - LOG.tracef("getOfflineUserSessionByBrokerUserIdStream(%s, %s)%s", realm, brokerUserId, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public long getOfflineSessionsCount(RealmModel realm, ClientModel client) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - LOG.tracef("getOfflineSessionsCount(%s, %s)%s", realm, client, getShortStackTrace()); - - return storeWithRealm(realm).getCount(withCriteria(mcb)); - } - - @Override - public Stream getOfflineUserSessionsStream(RealmModel realm, ClientModel client, - Integer firstResult, Integer maxResults) { - DefaultModelCriteria mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - - LOG.tracef("getOfflineUserSessionsStream(%s, %s, %s, %s)%s", realm, client, firstResult, maxResults, getShortStackTrace()); - - return storeWithRealm(realm).read(withCriteria(mcb).pagination(firstResult, maxResults, - UserSessionModel.SearchableFields.LAST_SESSION_REFRESH)) - .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull); - } - - @Override - public void importUserSessions(Collection persistentUserSessions, boolean offline) { - if (persistentUserSessions == null || persistentUserSessions.isEmpty()) { - return; - } - - persistentUserSessions.stream() - .map(pus -> { - MapUserSessionEntity userSessionEntity = createUserSessionEntityInstance(null, pus.getRealm().getId(), - pus.getUser().getId(), pus.getLoginUsername(), pus.getIpAddress(), pus.getAuthMethod(), - pus.isRememberMe(), pus.getBrokerSessionId(), pus.getBrokerUserId(), offline); - - for (Map.Entry entry : pus.getAuthenticatedClientSessions().entrySet()) { - MapAuthenticatedClientSessionEntity clientSession = createAuthenticatedClientSessionInstance(entry.getValue(), entry.getValue().getUserSession(), offline); - - // Update timestamp to same value as userSession. LastSessionRefresh of userSession from DB will have correct value - clientSession.setTimestamp(userSessionEntity.getLastSessionRefresh()); - userSessionEntity.addAuthenticatedClientSession(clientSession); - } - - return userSessionEntity; - }) - .forEach(userSessionTx::create); - } - - @Override - public void close() { - - } - - @Override - public int getStartupTime(RealmModel realm) { - return realm.getNotBefore(); - } - - /** - * Removes all online and offline user sessions that belong to the provided {@link RealmModel}. - * @param realm - */ - protected void removeAllUserSessions(RealmModel realm) { - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserSessionModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - - LOG.tracef("removeAllUserSessions(%s)%s", realm, getShortStackTrace()); - - storeWithRealm(realm).delete(withCriteria(mcb)); - } - - private Stream getOfflineUserSessionEntityStream(RealmModel realm, String userSessionId) { - if (userSessionId == null) { - return Stream.empty(); - } - - // first get a user entity by ID - DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(UserSessionModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(UserSessionModel.SearchableFields.ID, Operator.EQ, userSessionId); - - // check if it's an offline user session - MapUserSessionEntity userSessionEntity = storeWithRealm(realm).read(withCriteria(mcb)).findFirst().orElse(null); - if (userSessionEntity != null) { - if (Boolean.TRUE.equals(userSessionEntity.isOffline())) { - return Stream.of(userSessionEntity); - } - } else { - // no session found by the given ID, try to find by corresponding session ID - mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, Operator.EQ, userSessionId); - return storeWithRealm(realm).read(withCriteria(mcb)); - } - - // it's online user session so lookup offline user session by corresponding session id reference - String offlineUserSessionId = userSessionEntity.getNote(CORRESPONDING_SESSION_ID); - if (offlineUserSessionId != null) { - mcb = realmAndOfflineCriteriaBuilder(realm, true) - .compare(UserSessionModel.SearchableFields.ID, Operator.EQ, offlineUserSessionId); - return storeWithRealm(realm).read(withCriteria(mcb)); - } - - return Stream.empty(); - } - - private DefaultModelCriteria realmAndOfflineCriteriaBuilder(RealmModel realm, boolean offline) { - return DefaultModelCriteria.criteria() - .compare(UserSessionModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId()) - .compare(UserSessionModel.SearchableFields.IS_OFFLINE, Operator.EQ, offline); - } - - private MapUserSessionEntity getUserSessionById(RealmModel realm, String id) { - if (id == null) return null; - - MapUserSessionEntity userSessionEntity = transientUserSessions.get(id); - - if (userSessionEntity == null) { - MapUserSessionEntity userSession = storeWithRealm(realm).read(id); - return userSession; - } - return userSessionEntity; - } - - private MapUserSessionEntity createUserSessionEntityInstance(UserSessionModel userSession, boolean offline) { - MapUserSessionEntity entity = createUserSessionEntityInstance(null, userSession.getRealm().getId(), userSession.getUser().getId(), - userSession.getLoginUsername(), userSession.getIpAddress(), userSession.getAuthMethod(), userSession.isRememberMe(), - userSession.getBrokerSessionId(), userSession.getBrokerUserId(), offline); - - entity.setNotes(new ConcurrentHashMap<>(userSession.getNotes())); - entity.setNote(CORRESPONDING_SESSION_ID, userSession.getId()); - entity.setState(userSession.getState()); - entity.setTimestamp(TimeAdapter.fromSecondsToMilliseconds(userSession.getStarted())); - entity.setLastSessionRefresh(TimeAdapter.fromSecondsToMilliseconds(userSession.getLastSessionRefresh())); - - return entity; - } - - private MapAuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(AuthenticatedClientSessionModel clientSession, - UserSessionModel userSession, boolean offline) { - MapAuthenticatedClientSessionEntity entity = createAuthenticatedClientSessionEntityInstance(null, userSession.getId(), - clientSession.getRealm().getId(), clientSession.getClient().getId(), offline); - - entity.setAction(clientSession.getAction()); - entity.setAuthMethod(clientSession.getProtocol()); - - entity.setNotes(new ConcurrentHashMap<>(clientSession.getNotes())); - entity.setRedirectUri(clientSession.getRedirectUri()); - entity.setTimestamp(TimeAdapter.fromSecondsToMilliseconds(clientSession.getTimestamp())); - - return entity; - } - - private MapUserSessionEntity createUserSessionEntityInstance(String id, String realmId, String userId, String loginUsername, String ipAddress, - String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId, - boolean offline) { - MapUserSessionEntity userSessionEntity = DeepCloner.DUMB_CLONER.newInstance(MapUserSessionEntity.class); - userSessionEntity.setId(id); - userSessionEntity.setRealmId(realmId); - userSessionEntity.setUserId(userId); - userSessionEntity.setLoginUsername(loginUsername); - userSessionEntity.setIpAddress(ipAddress); - userSessionEntity.setAuthMethod(authMethod); - userSessionEntity.setRememberMe(rememberMe); - userSessionEntity.setBrokerSessionId(brokerSessionId); - userSessionEntity.setBrokerUserId(brokerUserId); - userSessionEntity.setOffline(offline); - userSessionEntity.setTimestamp(Time.currentTimeMillis()); - userSessionEntity.setLastSessionRefresh(userSessionEntity.getTimestamp()); - return userSessionEntity; - } - - private MapAuthenticatedClientSessionEntity createAuthenticatedClientSessionEntityInstance(String id, String userSessionId, String realmId, - String clientId, boolean offline) { - MapAuthenticatedClientSessionEntity clientSessionEntity = DeepCloner.DUMB_CLONER.newInstance(MapAuthenticatedClientSessionEntity.class); - clientSessionEntity.setId(id == null ? KeycloakModelUtils.generateId() : id); - clientSessionEntity.setRealmId(realmId); - clientSessionEntity.setClientId(clientId); - clientSessionEntity.setOffline(offline); - clientSessionEntity.setTimestamp(Time.currentTimeMillis()); - return clientSessionEntity; - } - -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProviderFactory.java deleted file mode 100644 index 20899bcb2ca..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProviderFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionProviderFactory; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.provider.InvalidationHandler; - -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE; -import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_BEFORE_REMOVE; - -/** - * @author Martin Kanis - */ -public class MapUserSessionProviderFactory extends AbstractMapProviderFactory implements UserSessionProviderFactory, InvalidationHandler { - - public MapUserSessionProviderFactory() { - super(UserSessionModel.class, MapUserSessionProvider.class); - } - - @Override - public MapUserSessionProvider createNew(KeycloakSession session) { - return new MapUserSessionProvider(session, getMapStorage(session)); - } - - @Override - public String getHelpText() { - return "User session provider"; - } - - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == USER_BEFORE_REMOVE) { - create(session).removeUserSessions((RealmModel) params[0], (UserModel) params[1]); - } else if (type == REALM_BEFORE_REMOVE) { - create(session).removeAllUserSessions((RealmModel) params[0]); - } - } - - @Override - public void loadPersistentSessions(KeycloakSessionFactory sessionFactory, int maxErrors, int sessionsPerSegment) { - - } -} diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/SessionExpiration.java b/model/map/src/main/java/org/keycloak/models/map/userSession/SessionExpiration.java deleted file mode 100644 index ca15d346cd6..00000000000 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/SessionExpiration.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.userSession; - -import org.keycloak.common.util.Time; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.common.TimeAdapter; -import org.keycloak.models.utils.SessionExpirationUtils; -import org.keycloak.protocol.oidc.OIDCConfigAttributes; - -/** - * @author Martin Kanis - */ -public class SessionExpiration { - - private static long getTimestampNote(MapAuthenticatedClientSessionEntity entity, String name) { - String value = entity.getNote(name); - if (value == null) { - // return timestamp if not found - return entity.getTimestamp(); - } - return TimeAdapter.fromSecondsToMilliseconds(Integer.parseInt(value)); - } - - public static void setClientSessionExpiration(MapAuthenticatedClientSessionEntity entity, RealmModel realm, ClientModel client) { - long timestampMillis = entity.getTimestamp() != null ? entity.getTimestamp() : 0L; - long clientSessionStartedAtMillis = getTimestampNote(entity, AuthenticatedClientSessionModel.STARTED_AT_NOTE); - long userSessionStartedAtMillis = getTimestampNote(entity, AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE); - boolean isRememberMe = Boolean.parseBoolean(entity.getNote(AuthenticatedClientSessionModel.USER_SESSION_REMEMBER_ME_NOTE)); - boolean isOffline = Boolean.TRUE.equals(entity.isOffline()); - - long expiresbyLifespan = SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(isOffline, isRememberMe, - clientSessionStartedAtMillis, userSessionStartedAtMillis, realm, client); - long expiresByIdle =SessionExpirationUtils.calculateClientSessionIdleTimestamp(isOffline, isRememberMe, timestampMillis, realm, client); - - if (expiresbyLifespan > 0) { - entity.setExpiration(Math.min(expiresbyLifespan, expiresByIdle)); - } else { - entity.setExpiration(expiresByIdle); - } - } - - public static void setUserSessionExpiration(MapUserSessionEntity entity, RealmModel realm) { - long timestampMillis = entity.getTimestamp() != null ? entity.getTimestamp() : 0L; - long lastSessionRefreshMillis = entity.getLastSessionRefresh() != null ? entity.getLastSessionRefresh() : 0L; - boolean isRememberMe = Boolean.TRUE.equals(entity.isRememberMe()); - boolean isOffline = Boolean.TRUE.equals(entity.isOffline()); - - long expiresByLifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(isOffline, isRememberMe, timestampMillis, realm); - long expiresByIdle = SessionExpirationUtils.calculateUserSessionIdleTimestamp(isOffline, isRememberMe, lastSessionRefreshMillis, realm); - - if (expiresByLifespan > 0) { - entity.setExpiration(Math.min(expiresByLifespan, expiresByIdle)); - } else { - entity.setExpiration(expiresByIdle); - } - } -} diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory deleted file mode 100644 index b68658f9936..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory +++ /dev/null @@ -1,19 +0,0 @@ -# -# JBoss, Home of Professional Open Source. -# Copyright 2021 Red Hat, Inc., and individual contributors -# as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.authorization.MapAuthorizationStoreFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.events.EventStoreProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.events.EventStoreProviderFactory deleted file mode 100644 index c9ed94d2216..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.events.EventStoreProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2016 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.events.MapEventStoreProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.keys.PublicKeyStorageProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.keys.PublicKeyStorageProviderFactory deleted file mode 100644 index 787dec09941..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.keys.PublicKeyStorageProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.ClientProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.ClientProviderFactory deleted file mode 100644 index c83466cb497..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.ClientProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.client.MapClientProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.ClientScopeProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.ClientScopeProviderFactory deleted file mode 100644 index af24ee869f6..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.ClientScopeProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.clientscope.MapClientScopeProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.DeploymentStateProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.DeploymentStateProviderFactory deleted file mode 100644 index 4689e26fdf0..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.DeploymentStateProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.GroupProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.GroupProviderFactory deleted file mode 100644 index 54f11f477b3..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.GroupProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.group.MapGroupProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory deleted file mode 100644 index 6b4fed7ac81..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.realm.MapRealmProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.RoleProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.RoleProviderFactory deleted file mode 100644 index c11da5a5e07..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.RoleProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.role.MapRoleProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.SingleUseObjectProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.SingleUseObjectProviderFactory deleted file mode 100644 index 8734b778e01..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.SingleUseObjectProviderFactory +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory - diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserLoginFailureProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserLoginFailureProviderFactory deleted file mode 100644 index b45b9c19fb9..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserLoginFailureProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory deleted file mode 100644 index 87133090ef2..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.user.MapUserProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserSessionProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserSessionProviderFactory deleted file mode 100644 index 55d75cd0fc8..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.UserSessionProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.userSession.MapUserSessionProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.locking.GlobalLockProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.locking.GlobalLockProviderFactory deleted file mode 100644 index 39c77b4f279..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.locking.GlobalLockProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2023 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.lock.MapGlobalLockProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory deleted file mode 100644 index 2b1a6743d99..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/map/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index 709abe5853e..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.storage.MapStorageSpi diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.sessions.AuthenticationSessionProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.sessions.AuthenticationSessionProviderFactory deleted file mode 100644 index d7ac3a5a5f0..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.sessions.AuthenticationSessionProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory \ No newline at end of file diff --git a/model/map/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory b/model/map/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory deleted file mode 100644 index bbcf7c396df..00000000000 --- a/model/map/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.models.map.datastore.MapDatastoreProviderFactory \ No newline at end of file diff --git a/model/map/src/test/java/org/keycloak/models/map/client/MapClientEntityClonerTest.java b/model/map/src/test/java/org/keycloak/models/map/client/MapClientEntityClonerTest.java deleted file mode 100644 index ad279239b94..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/client/MapClientEntityClonerTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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.client; - -import org.keycloak.models.map.common.DeepCloner; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.sameInstance; -import static org.keycloak.models.map.common.DeepCloner.DUMB_CLONER; - -/** - * - * @author hmlnarik - */ -public class MapClientEntityClonerTest { - - private final static DeepCloner CLONER = new DeepCloner.Builder() - .constructor(MapClientEntityImpl.class, MapClientEntityImpl::new) - .constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new) - .build(); - - @Test - public void testNewInstance() { - MapClientEntity newInstance = CLONER.newInstance(MapClientEntity.class); - assertThat(newInstance, instanceOf(MapClientEntityImpl.class)); - assertThat(newInstance.getId(), nullValue()); - } - - @Test - public void testNewInstanceWithId() { - MapClientEntity newInstance = CLONER.newInstance(MapClientEntity.class); - newInstance.setId("my-id"); - assertThat(newInstance, instanceOf(MapClientEntityImpl.class)); - assertThat(newInstance.getId(), is("my-id")); - } - - @Test - public void testCloneAsNewInstance() { - MapClientEntity newInstance = CLONER.newInstance(MapClientEntity.class); - newInstance.setId("my-id"); - newInstance.setClientId("a-client-id"); - newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc")); - - MapClientEntity clonedInstance = CLONER.from(newInstance); - assertThat(clonedInstance, instanceOf(MapClientEntityImpl.class)); - assertThat(clonedInstance.getId(), is("my-id")); - assertThat(clonedInstance.getClientId(), is("a-client-id")); - - assertThat(clonedInstance.getAttributes(), not(sameInstance(newInstance.getAttributes()))); - assertThat(clonedInstance.getAttributes().keySet(), containsInAnyOrder("attr")); - assertThat(clonedInstance.getAttributes().get("attr"), contains("aa", "bb", "cc")); - assertThat(clonedInstance.getAttributes().get("attr"), not(sameInstance(newInstance.getAttributes().get("attr")))); - - assertThat(clonedInstance.getAuthenticationFlowBindingOverrides(), nullValue()); - assertThat(clonedInstance.getRegistrationToken(), nullValue()); - } - - @Test - public void testCloneToExistingInstance() { - MapClientEntity newInstance = CLONER.newInstance(MapClientEntity.class); - newInstance.setId("my-id"); - newInstance.setClientId("a-client-id"); - newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc")); - MapProtocolMapperEntity pmm = DeepCloner.DUMB_CLONER.newInstance(MapProtocolMapperEntity.class); - pmm.setId("pmm-id"); - Map config = new HashMap<>(); - config.put("key1", "value1"); - config.put("key2", "value2"); - pmm.setConfig(config); - newInstance.addProtocolMapper(pmm); - newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc")); - - MapClientEntity clonedInstance = CLONER.newInstance(MapClientEntity.class); - assertThat(CLONER.deepCloneNoId(newInstance, clonedInstance), sameInstance(clonedInstance)); - assertThat(clonedInstance, instanceOf(MapClientEntityImpl.class)); - clonedInstance.setId("my-id2"); - assertThat(clonedInstance.getId(), is("my-id2")); - assertThat(clonedInstance.getClientId(), is("a-client-id")); - - assertThat(clonedInstance.getAttributes(), not(sameInstance(newInstance.getAttributes()))); - assertThat(clonedInstance.getAttributes().keySet(), containsInAnyOrder("attr")); - assertThat(clonedInstance.getAttributes().get("attr"), contains("aa", "bb", "cc")); - assertThat(clonedInstance.getAttributes().get("attr"), not(sameInstance(newInstance.getAttributes().get("attr")))); - - assertThat(clonedInstance.getProtocolMappers(), not(sameInstance(newInstance.getProtocolMappers()))); - assertThat(clonedInstance.getProtocolMapper("pmm-id").isPresent(), is(true)); - assertThat(newInstance.getProtocolMapper("pmm-id").isPresent(), is(true)); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get(), not(sameInstance(newInstance.getProtocolMapper("pmm-id").get()))); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get(), equalTo(newInstance.getProtocolMapper("pmm-id").get())); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get().getConfig(), not(sameInstance(newInstance.getProtocolMapper("pmm-id").get().getConfig()))); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get().getConfig(), equalTo(newInstance.getProtocolMapper("pmm-id").get().getConfig())); - - assertThat(clonedInstance.getAuthenticationFlowBindingOverrides(), nullValue()); - assertThat(clonedInstance.getRegistrationToken(), nullValue()); - } - - @Test - public void testCloneToExistingInstanceDumb() { - MapClientEntity newInstance = DeepCloner.DUMB_CLONER.newInstance(MapClientEntity.class); - newInstance.setId("my-id"); - newInstance.setClientId("a-client-id"); - newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc")); - MapProtocolMapperEntity pmm = DeepCloner.DUMB_CLONER.newInstance(MapProtocolMapperEntity.class); - pmm.setId("pmm-id"); - Map config = new HashMap<>(); - config.put("key1", "value1"); - config.put("key2", "value2"); - pmm.setConfig(config); - - newInstance.addProtocolMapper(pmm); - newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc")); - - MapClientEntity clonedInstance = CLONER.newInstance(MapClientEntity.class); - assertThat(CLONER.deepCloneNoId(newInstance, clonedInstance), sameInstance(clonedInstance)); - assertThat(clonedInstance, instanceOf(MapClientEntityImpl.class)); - clonedInstance.setId("my-id2"); - assertThat(clonedInstance.getId(), is("my-id2")); - assertThat(clonedInstance.getClientId(), is("a-client-id")); - - assertThat(clonedInstance.getAttributes(), not(sameInstance(newInstance.getAttributes()))); - assertThat(clonedInstance.getAttributes().keySet(), containsInAnyOrder("attr")); - assertThat(clonedInstance.getAttributes().get("attr"), contains("aa", "bb", "cc")); - assertThat(clonedInstance.getAttributes().get("attr"), not(sameInstance(newInstance.getAttributes().get("attr")))); - - assertThat(clonedInstance.getProtocolMappers(), not(sameInstance(newInstance.getProtocolMappers()))); - assertThat(clonedInstance.getProtocolMapper("pmm-id").isPresent(), is(true)); - assertThat(newInstance.getProtocolMapper("pmm-id").isPresent(), is(true)); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get(), not(sameInstance(newInstance.getProtocolMapper("pmm-id").get()))); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get(), equalTo(newInstance.getProtocolMapper("pmm-id").get())); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get().getConfig(), not(sameInstance(newInstance.getProtocolMapper("pmm-id").get().getConfig()))); - assertThat(clonedInstance.getProtocolMapper("pmm-id").get().getConfig(), equalTo(newInstance.getProtocolMapper("pmm-id").get().getConfig())); - - assertThat(clonedInstance.getAuthenticationFlowBindingOverrides(), nullValue()); - assertThat(clonedInstance.getRegistrationToken(), nullValue()); - } -} diff --git a/model/map/src/test/java/org/keycloak/models/map/common/StreamUtilsTest.java b/model/map/src/test/java/org/keycloak/models/map/common/StreamUtilsTest.java deleted file mode 100644 index 5b278409930..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/common/StreamUtilsTest.java +++ /dev/null @@ -1,83 +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.common; - -import org.keycloak.models.map.common.StreamUtils.Pair; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - * @author hmlnarik - */ -public class StreamUtilsTest { - - @Test - public void testLeftInnerJoinStream() { - Stream a = Stream.of(0,1,2,3,4); - Stream[] b = new Stream[] { - Stream.of(1,2), - Stream.of(1,2), - null, - null, - Stream.of(5, 6, 7), - }; - - try (Stream> res = StreamUtils.leftInnerJoinStream(a, n -> b[n])) { - final List> l = res.collect(Collectors.toList()); - assertEquals(l, Arrays.asList( - new Pair<>(0, 1), - new Pair<>(0, 2), - new Pair<>(1, 1), - new Pair<>(1, 2), - new Pair<>(4, 5), - new Pair<>(4, 6), - new Pair<>(4, 7) - )); - } - } - - @Test - public void testLeftInnerJoinIterable() { - Stream a = Stream.of(0,1,2,3,4); - Iterable[] b = new Iterable[] { - Arrays.asList(1,2), - Arrays.asList(1,2), - null, - null, - Arrays.asList(5, 6, 7), - }; - - try (Stream> res = StreamUtils.leftInnerJoinIterable(a, n -> b[n])) { - final List> l = res.collect(Collectors.toList()); - assertEquals(l, Arrays.asList( - new Pair<>(0, 1), - new Pair<>(0, 2), - new Pair<>(1, 1), - new Pair<>(1, 2), - new Pair<>(4, 5), - new Pair<>(4, 6), - new Pair<>(4, 7) - )); - } - } - -} diff --git a/model/map/src/test/java/org/keycloak/models/map/common/TimeAdapterTest.java b/model/map/src/test/java/org/keycloak/models/map/common/TimeAdapterTest.java deleted file mode 100644 index d37e82fe44b..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/common/TimeAdapterTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022. 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.common; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Alexander Schwartz - */ -public class TimeAdapterTest { - - @Test - public void shouldConvertIntegersToLongs() { - Assert.assertEquals(0, TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(0)); - Assert.assertEquals(1, TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(1)); - Assert.assertEquals(Integer.MAX_VALUE, TimeAdapter.fromIntegerWithTimeInSecondsToLongWithTimeAsInSeconds(Integer.MAX_VALUE)); - } - - @Test - public void shouldConvertLongsToIntegersSafely() { - Assert.assertEquals(0, TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds((long) 0)); - Assert.assertEquals(1, TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds((long) 1)); - Assert.assertEquals(Integer.MAX_VALUE, TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds((long) Integer.MAX_VALUE)); - Assert.assertEquals(Integer.MAX_VALUE, TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(Long.MAX_VALUE)); - } - -} \ No newline at end of file diff --git a/model/map/src/test/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProviderCacheTest.java b/model/map/src/test/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProviderCacheTest.java deleted file mode 100644 index 24ba81e4b1f..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProviderCacheTest.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * 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.common.delegate; - -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityFields; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.storage.tree.NodeProperties; -import org.keycloak.models.map.storage.tree.TreeStorageNodeInstance; -import org.keycloak.models.map.storage.tree.TreeStorageNodePrescription; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Before; -import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; - -/** - * - * @author hmlnarik - */ -public class PerFieldDelegateProviderCacheTest { - - private MapClientEntity upperEnt; - private MapClientEntity lowerEnt; - - private HashMap upperNodeProperties; - private EnumMap upperCacheFor; - private EnumMap upperCacheForExcluded; - - private HashMap lowerNodeProperties; - private EnumMap lowerCacheFor; - private EnumMap lowerCacheForExcluded; - - private TreeStorageNodeInstance upperTsni; - private TreeStorageNodeInstance lowerTsni; - - AtomicInteger lowerEntSupplierCallCount = new AtomicInteger(); - - @Before - public void initEntities() { - upperEnt = DeepCloner.DUMB_CLONER.newInstance(MapClientEntity.class); - lowerEnt = DeepCloner.DUMB_CLONER.newInstance(MapClientEntity.class); - - upperEnt.setProtocol("upper-protocol"); - upperEnt.addRedirectUri("upper-redirectUri-1"); - upperEnt.addRedirectUri("upper-redirectUri-2"); - upperEnt.setClientId("upper-clientId-1"); - upperEnt.setAttribute("attr1", Arrays.asList("upper-value-1")); - upperEnt.setAttribute("attr2", Arrays.asList("upper-value-2")); - upperEnt.setAttribute("attr3", Arrays.asList("upper-value-3")); - - lowerEnt.setProtocol("lower-protocol"); - lowerEnt.addRedirectUri("lower-redirectUri-1"); - lowerEnt.addRedirectUri("lower-redirectUri-2"); - lowerEnt.setClientId("lower-clientId-1"); - lowerEnt.setAttribute("attr1", Arrays.asList("lower-value-1")); - lowerEnt.setAttribute("attr3", Arrays.asList("lower-value-3")); - lowerEnt.setAttribute("attr4", Arrays.asList("lower-value-4")); - - upperNodeProperties = new HashMap<>(); - upperCacheFor = new EnumMap<>(MapClientEntityFields.class); - upperCacheForExcluded = new EnumMap<>(MapClientEntityFields.class); - - lowerNodeProperties = new HashMap<>(); - lowerCacheFor = new EnumMap<>(MapClientEntityFields.class); - lowerCacheForExcluded = new EnumMap<>(MapClientEntityFields.class); - - lowerEntSupplierCallCount.set(0); - } - - private MapClientEntity prepareEntityAndTreeNodeInstances() { - TreeStorageNodePrescription upperTsnp = new TreeStorageNodePrescription(upperNodeProperties, null, null); - TreeStorageNodePrescription lowerTsnp = new TreeStorageNodePrescription(lowerNodeProperties, null, null); - - upperTsni = new TreeStorageNodeInstance<>(null, upperTsnp); - lowerTsni = new TreeStorageNodeInstance<>(null, lowerTsnp); - - PerFieldDelegateProvider fieldProvider = new PerFieldDelegateProvider<>(upperTsni.new WithEntity(upperEnt), () -> { - lowerEntSupplierCallCount.incrementAndGet(); - return lowerEnt; - }); - return DeepCloner.DUMB_CLONER.entityFieldDelegate(MapClientEntity.class, fieldProvider); - } - - @Test - public void testGet_CacheFor() { - // - // High-level perspective: cache for listed fields, the primary source for values is in the lower entity. - // - // Upper node is not a primary source for any fields, and caches only the enumerated fields - // Lower node is a primary source for all fields - // There is an (intentional) discrepancy between the field values in lowerEnt and upperEnt to be able to distinguish - // which entity the return value was obtained from. - upperCacheFor.put(MapClientEntityFields.CLIENT_ID, null); - upperCacheFor.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.CACHE_FOR, upperCacheFor); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR, Collections.emptyMap()); // set the upper object to be exclusively a cache - none of the fields is primary source - - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getClientId(), is("upper-clientId-1")); - - assertThat(ent.getAttribute("attr2"), contains("upper-value-2")); - assertThat(ent.getAttribute("attr3"), contains("upper-value-3")); - assertThat(ent.getAttribute("attr4"), nullValue()); - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getProtocol(), is("lower-protocol")); - assertThat(ent.getAttribute("attr1"), contains("lower-value-1")); - assertThat(lowerEntSupplierCallCount.get(), is(1)); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3")); - assertThat(ent.getAttributes(), hasEntry("attr1", Arrays.asList("lower-value-1"))); - assertThat(ent.getAttributes(), hasEntry("attr2", Arrays.asList("upper-value-2"))); - assertThat(ent.getAttributes(), hasEntry("attr3", Arrays.asList("upper-value-3"))); - - assertThat(lowerEntSupplierCallCount.get(), is(1)); - } - - @Test - public void testSet_CacheFor() { - // - // High-level perspective: fields available in the lower layer (e.g. LDAP) cached in a faster layer (e.g. database) - // - // Upper node is a primary source for all fields apart from cached ones. It only caches only the enumerated fields - // Lower node is a primary source for all fields - // There is an (intentional) discrepancy between the field values in lowerEnt and upperEnt to be able to distinguish - // which entity the return value was obtained from. - upperCacheFor.put(MapClientEntityFields.CLIENT_ID, null); - upperCacheFor.put(MapClientEntityFields.PROTOCOL, null); - upperCacheFor.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.CACHE_FOR, upperCacheFor); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, upperCacheFor); - - // When there is primary source in the node properties, named properties are considered as owned by this entity, and - // all other are considered as owned by child entity. - // Thus there is no call to the child entity creator. - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - // When - ent.setProtocol("modified-protocol"); // modification in: upper and lower - ent.setAttribute("attr1", Arrays.asList("modified-value-1")); // modification in: upper - ent.setAttribute("attrX", Arrays.asList("modified-value-X")); // modification in: upper - ent.addRedirectUri("added-redirectUri"); // modification in: upper - ent.removeRedirectUri("upper-redirectUri-2"); // modification in: upper - ent.removeRedirectUri("lower-redirectUri-2"); // modification in: upper - - // Then - assertThat(lowerEntSupplierCallCount.get(), is(1)); - - assertThat(ent.getClientId(), is("upper-clientId-1")); - assertThat(upperEnt.getClientId(), is("upper-clientId-1")); - assertThat(lowerEnt.getClientId(), is("lower-clientId-1")); - - assertThat(ent.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(upperEnt.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(lowerEnt.getRedirectUris(), containsInAnyOrder("lower-redirectUri-1", "lower-redirectUri-2")); - - assertThat(ent.getProtocol(), is("modified-protocol")); - assertThat(upperEnt.getProtocol(), is("modified-protocol")); - assertThat(lowerEnt.getProtocol(), is("modified-protocol")); - - assertThat(ent.getAttribute("attr1"), contains("modified-value-1")); - assertThat(upperEnt.getAttribute("attr1"), contains("modified-value-1")); - assertThat(lowerEnt.getAttribute("attr1"), contains("lower-value-1")); - - assertThat(ent.getAttribute("attr3"), contains("upper-value-3")); - assertThat(upperEnt.getAttribute("attr3"), contains("upper-value-3")); - assertThat(lowerEnt.getAttribute("attr3"), contains("lower-value-3")); - - assertThat(ent.getAttribute("attrX"), contains("modified-value-X")); - assertThat(upperEnt.getAttribute("attrX"), contains("modified-value-X")); - assertThat(lowerEnt.getAttribute("attrX"), nullValue()); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder( - "attr1", // From upper - "attr2", // From upper, since it is a "cached" value (deliberately a stale cache here, it is not in lower) - "attr3", // From upper - // "attr4", // From upper where it has no value (deliberately a stale cache here, it has a value in lower) - "attrX" // From upper - )); - assertThat(upperEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3", "attrX")); - assertThat(lowerEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4")); - } - - @Test - public void testGet_CacheForExcluded() { - // - // High-level perspective: listed fields exclusively available in the lower layer (e.g. LDAP), - // never stored nor cached in the top layer (e.g. database). - // All other fields are stored in the top layer. - // - // Upper node is a primary source for all fields apart from cached ones. It only caches only the enumerated fields - // Lower node is a primary source for all fields - // There is an (intentional) discrepancy between the field values in lowerEnt and upperEnt to be able to distinguish - // which entity the return value was obtained from. - upperCacheForExcluded.put(MapClientEntityFields.CLIENT_ID, null); - upperCacheForExcluded.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.CACHE_FOR_EXCLUDED, upperCacheForExcluded); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, upperCacheForExcluded); - - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - assertThat(ent.getProtocol(), is("upper-protocol")); - assertThat(ent.getAttribute("attr1"), contains("upper-value-1")); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getAttribute("attr2"), nullValue()); - assertThat(lowerEntSupplierCallCount.get(), is(1)); - assertThat(ent.getAttribute("attr3"), contains("lower-value-3")); - assertThat(ent.getAttribute("attr4"), contains("lower-value-4")); - - assertThat(ent.getClientId(), is("lower-clientId-1")); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4")); - assertThat(ent.getAttributes(), hasEntry("attr1", Arrays.asList("upper-value-1"))); - assertThat(ent.getAttributes(), hasEntry("attr3", Arrays.asList("lower-value-3"))); - assertThat(ent.getAttributes(), hasEntry("attr4", Arrays.asList("lower-value-4"))); - - assertThat(lowerEntSupplierCallCount.get(), is(1)); - } - - @Test - public void testSet_CacheForExcluded() { - // - // High-level perspective: listed fields exclusively available in the lower layer (e.g. LDAP), - // never stored nor cached in the top layer (e.g. database). - // All other fields are stored in the top layer. - // - // When there is are primary source exclusion in the node properties, all properties apart from those enumerated - // are considered as owned by this entity. Those enumerated are obtained from the child entity. - // Thus there must be a single call to the child entity creator. - upperCacheForExcluded.put(MapClientEntityFields.CLIENT_ID, null); - upperCacheForExcluded.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.CACHE_FOR_EXCLUDED, upperCacheForExcluded); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, upperCacheForExcluded); - - // When there is primary source exclusion in the node properties, listed properties are considered as owned by the child - // entity, and all other are considered as owned by this entity. - // Thus there is no call to the child entity creator. - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - // When - ent.setClientId("modified-client-id-1"); // modification in: lower - ent.setProtocol("modified-protocol"); // modification in: upper - ent.setAttribute("attr4", Arrays.asList("modified-value-4")); // modification in: lower - ent.setAttribute("attrX", Arrays.asList("modified-value-X")); // modification in: upper - ent.addRedirectUri("added-redirectUri"); // modification in: upper - ent.removeRedirectUri("upper-redirectUri-2"); // modification in: upper - ent.removeRedirectUri("lower-redirectUri-2"); // modification in: upper - - // Then - assertThat(lowerEntSupplierCallCount.get(), is(1)); - - assertThat(ent.getClientId(), is("modified-client-id-1")); - assertThat(upperEnt.getClientId(), is("upper-clientId-1")); - assertThat(lowerEnt.getClientId(), is("modified-client-id-1")); - - assertThat(ent.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(upperEnt.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(lowerEnt.getRedirectUris(), containsInAnyOrder("lower-redirectUri-1", "lower-redirectUri-2")); - - assertThat(ent.getProtocol(), is("modified-protocol")); - assertThat(upperEnt.getProtocol(), is("modified-protocol")); - assertThat(lowerEnt.getProtocol(), is("lower-protocol")); - - assertThat(ent.getAttribute("attr1"), contains("upper-value-1")); - assertThat(upperEnt.getAttribute("attr1"), contains("upper-value-1")); - assertThat(lowerEnt.getAttribute("attr1"), contains("lower-value-1")); - - assertThat(ent.getAttribute("attr2"), nullValue()); - assertThat(upperEnt.getAttribute("attr2"), contains("upper-value-2")); - assertThat(lowerEnt.getAttribute("attr2"), nullValue()); - - assertThat(ent.getAttribute("attr3"), contains("lower-value-3")); - assertThat(upperEnt.getAttribute("attr3"), contains("upper-value-3")); - assertThat(lowerEnt.getAttribute("attr3"), contains("lower-value-3")); - - assertThat(ent.getAttribute("attr4"), contains("modified-value-4")); - assertThat(upperEnt.getAttribute("attr4"), nullValue()); - assertThat(lowerEnt.getAttribute("attr4"), contains("modified-value-4")); - - assertThat(ent.getAttribute("attrX"), contains("modified-value-X")); - assertThat(upperEnt.getAttribute("attrX"), contains("modified-value-X")); - assertThat(lowerEnt.getAttribute("attrX"), nullValue()); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder( - "attr1", // From upper - // "attr2", // From lower where it has no value - "attr3", // From lower - "attr4", // From lower - "attrX" // From upper - )); - assertThat(upperEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3", "attrX")); - assertThat(lowerEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4")); - } - -} diff --git a/model/map/src/test/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProviderPrimarySourceTest.java b/model/map/src/test/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProviderPrimarySourceTest.java deleted file mode 100644 index 466581b5f32..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/common/delegate/PerFieldDelegateProviderPrimarySourceTest.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * 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.common.delegate; - -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityFields; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.storage.tree.NodeProperties; -import org.keycloak.models.map.storage.tree.TreeStorageNodeInstance; -import org.keycloak.models.map.storage.tree.TreeStorageNodePrescription; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Before; -import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; - -/** - * - * @author hmlnarik - */ -public class PerFieldDelegateProviderPrimarySourceTest { - - private MapClientEntity upperEnt; - private MapClientEntity lowerEnt; - - private HashMap upperNodeProperties; - private EnumMap upperPrimarySourceFor; - private EnumMap upperPrimarySourceForExcluded; - - private HashMap lowerNodeProperties; - private EnumMap lowerPrimarySourceFor; - private EnumMap lowerPrimarySourceForExcluded; - - private TreeStorageNodeInstance upperTsni; - private TreeStorageNodeInstance lowerTsni; - - AtomicInteger lowerEntSupplierCallCount = new AtomicInteger(); - - @Before - public void initEntities() { - upperEnt = DeepCloner.DUMB_CLONER.newInstance(MapClientEntity.class); - lowerEnt = DeepCloner.DUMB_CLONER.newInstance(MapClientEntity.class); - - upperEnt.setProtocol("upper-protocol"); - upperEnt.addRedirectUri("upper-redirectUri-1"); - upperEnt.addRedirectUri("upper-redirectUri-2"); - upperEnt.setClientId("upper-clientId-1"); - upperEnt.setAttribute("attr1", Arrays.asList("upper-value-1")); - upperEnt.setAttribute("attr2", Arrays.asList("upper-value-2")); - upperEnt.setAttribute("attr3", Arrays.asList("upper-value-3")); - - lowerEnt.setProtocol("lower-protocol"); - lowerEnt.addRedirectUri("lower-redirectUri-1"); - lowerEnt.addRedirectUri("lower-redirectUri-2"); - lowerEnt.setClientId("lower-clientId-1"); - lowerEnt.setAttribute("attr1", Arrays.asList("lower-value-1")); - lowerEnt.setAttribute("attr3", Arrays.asList("lower-value-3")); - lowerEnt.setAttribute("attr4", Arrays.asList("lower-value-4")); - - upperNodeProperties = new HashMap<>(); - upperPrimarySourceFor = new EnumMap<>(MapClientEntityFields.class); - upperPrimarySourceForExcluded = new EnumMap<>(MapClientEntityFields.class); - - lowerNodeProperties = new HashMap<>(); - lowerPrimarySourceFor = new EnumMap<>(MapClientEntityFields.class); - lowerPrimarySourceForExcluded = new EnumMap<>(MapClientEntityFields.class); - - lowerEntSupplierCallCount.set(0); - } - - private MapClientEntity prepareEntityAndTreeNodeInstances() { - TreeStorageNodePrescription upperTsnp = new TreeStorageNodePrescription(upperNodeProperties, null, null); - TreeStorageNodePrescription lowerTsnp = new TreeStorageNodePrescription(lowerNodeProperties, null, null); - - upperTsni = new TreeStorageNodeInstance<>(null, upperTsnp); - lowerTsni = new TreeStorageNodeInstance<>(null, lowerTsnp); - - PerFieldDelegateProvider fieldProvider = new PerFieldDelegateProvider<>(upperTsni.new WithEntity(upperEnt), () -> { - lowerEntSupplierCallCount.incrementAndGet(); - return lowerEnt; - }); - return DeepCloner.DUMB_CLONER.entityFieldDelegate(MapClientEntity.class, fieldProvider); - } - - @Test - public void testGet_NoPrimarySource() { - // When there is no primary source (exclusion) in the node properties, all properties are considered as owned by this entity. - // Thus there is no call to the child entity creator. - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - assertThat(ent.getProtocol(), is("upper-protocol")); - assertThat(ent.getAttribute("attr1"), contains("upper-value-1")); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getAttribute("attr2"), contains("upper-value-2")); - assertThat(ent.getAttribute("attr3"), contains("upper-value-3")); - assertThat(ent.getAttribute("attr4"), nullValue()); - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getClientId(), is("upper-clientId-1")); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3")); - assertThat(ent.getAttributes(), hasEntry("attr1", Arrays.asList("upper-value-1"))); - assertThat(ent.getAttributes(), hasEntry("attr2", Arrays.asList("upper-value-2"))); - assertThat(ent.getAttributes(), hasEntry("attr3", Arrays.asList("upper-value-3"))); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - } - - @Test - public void testSet_NoPrimarySource() { - // When there is no primary source (exclusion) in the node properties, all properties are considered as owned by this entity. - // Thus there is no call to the child entity creator. - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - // When - ent.setProtocol("modified-protocol"); // modification in: upper - ent.setAttribute("attr1", Arrays.asList("modified-value-1")); // modification in: upper - ent.setAttribute("attrX", Arrays.asList("modified-value-X")); // modification in: upper - ent.addRedirectUri("added-redirectUri"); // modification in: upper - ent.removeRedirectUri("upper-redirectUri-2"); // modification in: upper - ent.removeRedirectUri("lower-redirectUri-2"); // modification in: upper - - // Then - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getClientId(), is("upper-clientId-1")); - assertThat(upperEnt.getClientId(), is("upper-clientId-1")); - assertThat(lowerEnt.getClientId(), is("lower-clientId-1")); - - assertThat(ent.getProtocol(), is("modified-protocol")); - assertThat(upperEnt.getProtocol(), is("modified-protocol")); - assertThat(lowerEnt.getProtocol(), is("lower-protocol")); - - assertThat(ent.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(upperEnt.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(lowerEnt.getRedirectUris(), containsInAnyOrder("lower-redirectUri-1", "lower-redirectUri-2")); - - assertThat(ent.getAttribute("attr1"), contains("modified-value-1")); - assertThat(upperEnt.getAttribute("attr1"), contains("modified-value-1")); - assertThat(lowerEnt.getAttribute("attr1"), contains("lower-value-1")); - - assertThat(ent.getAttribute("attr3"), contains("upper-value-3")); - assertThat(upperEnt.getAttribute("attr3"), contains("upper-value-3")); - assertThat(lowerEnt.getAttribute("attr3"), contains("lower-value-3")); - - assertThat(ent.getAttribute("attrX"), contains("modified-value-X")); - assertThat(upperEnt.getAttribute("attrX"), contains("modified-value-X")); - assertThat(lowerEnt.getAttribute("attrX"), nullValue()); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3", "attrX")); - assertThat(upperEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3", "attrX")); - assertThat(lowerEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4")); - } - - @Test - public void testGet_PrimarySourceFor() { - // - // High-level perspective: only listed fields are available in upper entity, the rest is in lower one - // - // When there is are primary source in the node properties, only properties from those listed - // are considered as owned by this entity. Those not listed are obtained from the child entity. - // Thus there must be a single call to the child entity creator. - upperPrimarySourceFor.put(MapClientEntityFields.CLIENT_ID, null); - upperPrimarySourceFor.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR, upperPrimarySourceFor); - - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getClientId(), is("upper-clientId-1")); - - assertThat(ent.getAttribute("attr2"), contains("upper-value-2")); - assertThat(ent.getAttribute("attr3"), contains("upper-value-3")); - assertThat(ent.getAttribute("attr4"), nullValue()); - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getProtocol(), is("lower-protocol")); - assertThat(ent.getAttribute("attr1"), contains("lower-value-1")); - assertThat(lowerEntSupplierCallCount.get(), is(1)); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3")); - assertThat(ent.getAttributes(), hasEntry("attr1", Arrays.asList("lower-value-1"))); - assertThat(ent.getAttributes(), hasEntry("attr2", Arrays.asList("upper-value-2"))); - assertThat(ent.getAttributes(), hasEntry("attr3", Arrays.asList("upper-value-3"))); - - assertThat(lowerEntSupplierCallCount.get(), is(1)); - } - - @Test - public void testSet_PrimarySourceFor() { - // - // High-level perspective: only listed fields are available in upper entity, the rest is in lower one - // - // When there is are primary source in the node properties, only properties from those enumerated - // are considered as owned by this entity. Those not listed are obtained from the child entity. - // Thus there must be a single call to the child entity creator. - upperPrimarySourceFor.put(MapClientEntityFields.CLIENT_ID, null); - upperPrimarySourceFor.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR, upperPrimarySourceFor); - - // When there is primary source in the node properties, named properties are considered as owned by this entity, and - // all other are considered as owned by child entity. - // Thus there is no call to the child entity creator. - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - // When - ent.setClientId("modified-client-id"); // modification in: upper - ent.setProtocol("modified-protocol"); // modification in: lower - ent.setAttribute("attr1", Arrays.asList("modified-value-1")); // modification in: lower - ent.setAttribute("attrX", Arrays.asList("modified-value-X")); // modification in: lower - ent.addRedirectUri("added-redirectUri"); // modification in: lower - ent.removeRedirectUri("upper-redirectUri-2"); // modification in: lower - ent.removeRedirectUri("lower-redirectUri-2"); // modification in: lower - - // Then - assertThat(lowerEntSupplierCallCount.get(), is(1)); - - assertThat(ent.getClientId(), is("modified-client-id")); - assertThat(upperEnt.getClientId(), is("modified-client-id")); - assertThat(lowerEnt.getClientId(), is("lower-clientId-1")); - - assertThat(ent.getRedirectUris(), containsInAnyOrder("lower-redirectUri-1", "added-redirectUri")); - assertThat(upperEnt.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "upper-redirectUri-2")); - assertThat(lowerEnt.getRedirectUris(), containsInAnyOrder("lower-redirectUri-1", "added-redirectUri")); - - assertThat(ent.getProtocol(), is("modified-protocol")); - assertThat(upperEnt.getProtocol(), is("upper-protocol")); - assertThat(lowerEnt.getProtocol(), is("modified-protocol")); - - assertThat(ent.getAttribute("attr1"), contains("modified-value-1")); - assertThat(upperEnt.getAttribute("attr1"), contains("upper-value-1")); - assertThat(lowerEnt.getAttribute("attr1"), contains("modified-value-1")); - - assertThat(ent.getAttribute("attr3"), contains("upper-value-3")); - assertThat(upperEnt.getAttribute("attr3"), contains("upper-value-3")); - assertThat(lowerEnt.getAttribute("attr3"), contains("lower-value-3")); - - assertThat(ent.getAttribute("attrX"), contains("modified-value-X")); - assertThat(upperEnt.getAttribute("attrX"), nullValue()); - assertThat(lowerEnt.getAttribute("attrX"), contains("modified-value-X")); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder( - "attr1", // From lower - "attr2", // From upper - "attr3", // From upper - // "attr4", // From upper where it has no value - "attrX" // From lower - )); - assertThat(upperEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3")); - assertThat(lowerEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4", "attrX")); - } - - @Test - public void testGet_PrimarySourceForExcluded() { - // - // High-level perspective: all but the listed fields are available in upper entity (e.g. JPA), - // the listed ones are stored in the lower entity (e.g. LDAP) - // - // When there is are primary source exclusion in the node properties, all properties apart from those enumerated - // are considered as owned by this entity. Those enumerated are obtained from the child entity. - // Thus there must be a single call to the child entity creator. - upperPrimarySourceForExcluded.put(MapClientEntityFields.CLIENT_ID, null); - upperPrimarySourceForExcluded.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, upperPrimarySourceForExcluded); - - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - assertThat(ent.getProtocol(), is("upper-protocol")); - assertThat(ent.getAttribute("attr1"), contains("upper-value-1")); - - assertThat(lowerEntSupplierCallCount.get(), is(0)); - - assertThat(ent.getAttribute("attr2"), nullValue()); - assertThat(lowerEntSupplierCallCount.get(), is(1)); - assertThat(ent.getAttribute("attr3"), contains("lower-value-3")); - assertThat(ent.getAttribute("attr4"), contains("lower-value-4")); - - assertThat(ent.getClientId(), is("lower-clientId-1")); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4")); - assertThat(ent.getAttributes(), hasEntry("attr1", Arrays.asList("upper-value-1"))); - assertThat(ent.getAttributes(), hasEntry("attr3", Arrays.asList("lower-value-3"))); - assertThat(ent.getAttributes(), hasEntry("attr4", Arrays.asList("lower-value-4"))); - - assertThat(lowerEntSupplierCallCount.get(), is(1)); - } - - @Test - public void testSet_PrimarySourceForExcluded() { - // - // High-level perspective: all but the listed fields are available in upper entity (e.g. JPA), - // the listed ones are stored in the lower entity (e.g. LDAP) - // - // When there is are primary source exclusion in the node properties, all properties apart from those enumerated - // are considered as owned by this entity. Those enumerated are obtained from the child entity. - // Thus there must be a single call to the child entity creator. - upperPrimarySourceForExcluded.put(MapClientEntityFields.CLIENT_ID, null); - upperPrimarySourceForExcluded.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr2", "attr3", "attr4")); - upperNodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, upperPrimarySourceForExcluded); - - // When there is primary source exclusion in the node properties, listed properties are considered as owned by the child - // entity, and all other are considered as owned by this entity. - // Thus there is no call to the child entity creator. - MapClientEntity ent = prepareEntityAndTreeNodeInstances(); - - // When - ent.setClientId("modified-client-id-1"); // modification in: lower - ent.setProtocol("modified-protocol"); // modification in: upper - ent.setAttribute("attr4", Arrays.asList("modified-value-4")); // modification in: lower - ent.setAttribute("attrX", Arrays.asList("modified-value-X")); // modification in: upper - ent.addRedirectUri("added-redirectUri"); // modification in: upper - ent.removeRedirectUri("upper-redirectUri-2"); // modification in: upper - ent.removeRedirectUri("lower-redirectUri-2"); // modification in: upper - - // Then - assertThat(lowerEntSupplierCallCount.get(), is(1)); - - assertThat(ent.getClientId(), is("modified-client-id-1")); - assertThat(upperEnt.getClientId(), is("upper-clientId-1")); - assertThat(lowerEnt.getClientId(), is("modified-client-id-1")); - - assertThat(ent.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(upperEnt.getRedirectUris(), containsInAnyOrder("upper-redirectUri-1", "added-redirectUri")); - assertThat(lowerEnt.getRedirectUris(), containsInAnyOrder("lower-redirectUri-1", "lower-redirectUri-2")); - - assertThat(ent.getProtocol(), is("modified-protocol")); - assertThat(upperEnt.getProtocol(), is("modified-protocol")); - assertThat(lowerEnt.getProtocol(), is("lower-protocol")); - - assertThat(ent.getAttribute("attr1"), contains("upper-value-1")); - assertThat(upperEnt.getAttribute("attr1"), contains("upper-value-1")); - assertThat(lowerEnt.getAttribute("attr1"), contains("lower-value-1")); - - assertThat(ent.getAttribute("attr2"), nullValue()); - assertThat(upperEnt.getAttribute("attr2"), contains("upper-value-2")); - assertThat(lowerEnt.getAttribute("attr2"), nullValue()); - - assertThat(ent.getAttribute("attr3"), contains("lower-value-3")); - assertThat(upperEnt.getAttribute("attr3"), contains("upper-value-3")); - assertThat(lowerEnt.getAttribute("attr3"), contains("lower-value-3")); - - assertThat(ent.getAttribute("attr4"), contains("modified-value-4")); - assertThat(upperEnt.getAttribute("attr4"), nullValue()); - assertThat(lowerEnt.getAttribute("attr4"), contains("modified-value-4")); - - assertThat(ent.getAttribute("attrX"), contains("modified-value-X")); - assertThat(upperEnt.getAttribute("attrX"), contains("modified-value-X")); - assertThat(lowerEnt.getAttribute("attrX"), nullValue()); - - assertThat(ent.getAttributes().keySet(), containsInAnyOrder( - "attr1", // From upper - // "attr2", // From lower where it has no value - "attr3", // From lower - "attr4", // From lower - "attrX" // From lower - )); - assertThat(upperEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2", "attr3", "attrX")); - assertThat(lowerEnt.getAttributes().keySet(), containsInAnyOrder("attr1", "attr3", "attr4")); - } - -} diff --git a/model/map/src/test/java/org/keycloak/models/map/realm/RealmEntityUndefinedValuesTest.java b/model/map/src/test/java/org/keycloak/models/map/realm/RealmEntityUndefinedValuesTest.java deleted file mode 100644 index 84e3e850dc3..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/realm/RealmEntityUndefinedValuesTest.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2022 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.realm; - -import org.keycloak.models.map.common.DeepCloner; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.aMapWithSize; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasKey; - -public class RealmEntityUndefinedValuesTest { - - public MapRealmEntity newMapRealmEntity() { - return DeepCloner.DUMB_CLONER.newInstance(MapRealmEntity.class); - } - - @Test - public void testUndefinedValuesToCollection() { - // setup - MapRealmEntity realmEntity = newMapRealmEntity(); - { - // when - realmEntity.setEventsListeners(null); - - // then - assertThat(realmEntity.getEventsListeners(), nullValue()); - } - { - // when - realmEntity.setEventsListeners(Collections.emptySet()); - - // then - assertThat(realmEntity.getEventsListeners(), nullValue()); - } - { - // when - realmEntity.setEventsListeners(Collections.singleton(null)); - - // then - assertThat(realmEntity.getEventsListeners(), nullValue()); - } - { - // when - realmEntity.setEventsListeners(Collections.singleton("listener1")); - - // then - assertThat(realmEntity.getEventsListeners(), contains("listener1")); - } - { - // when - realmEntity.setEventsListeners(Collections.emptySet()); - - // then - assertThat(realmEntity.getEventsListeners(), nullValue()); - } - { - // when - realmEntity.addOptionalClientScopeId(null); - - // then - assertThat(realmEntity.getOptionalClientScopeIds(), nullValue()); - } - { - // when - realmEntity.addOptionalClientScopeId("id1"); - - // then - assertThat(realmEntity.getOptionalClientScopeIds(), notNullValue()); - assertThat(realmEntity.getOptionalClientScopeIds(), contains("id1")); - } - { - // when - realmEntity.addOptionalClientScopeId(null); - - // then - assertThat(realmEntity.getOptionalClientScopeIds(), notNullValue()); - assertThat(realmEntity.getOptionalClientScopeIds(), contains("id1")); - } - } - - @Test - public void testAddUndefinedValuesToMapStringString() { - // setup - MapRealmEntity realmEntity = newMapRealmEntity(); - Map headers = new HashMap<>(); - - { - // when - realmEntity.setBrowserSecurityHeaders(Collections.emptyMap()); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), nullValue()); - } - { - // when - headers.put("key1", null); - realmEntity.setBrowserSecurityHeaders(headers); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), nullValue()); - } - { - // when - headers.put("key1", "value1"); - realmEntity.setBrowserSecurityHeaders(headers); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), notNullValue()); - assertThat(realmEntity.getBrowserSecurityHeaders(), allOf(aMapWithSize(1), hasEntry(equalTo("key1"), equalTo("value1")))); - } - { - // when - headers.put("key2", null); - realmEntity.setBrowserSecurityHeaders(headers); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), notNullValue()); - assertThat(realmEntity.getBrowserSecurityHeaders(), allOf(aMapWithSize(1), hasEntry(equalTo("key1"), equalTo("value1")))); - } - { - // when - realmEntity.setBrowserSecurityHeaders(Collections.emptyMap()); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), nullValue()); - } - { - // when - realmEntity.setBrowserSecurityHeader("key1", null); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), nullValue()); - } - { - // when - realmEntity.setBrowserSecurityHeader("key1", "value1"); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), allOf(aMapWithSize(1), hasEntry(equalTo("key1"), equalTo("value1")))); - } - { - // when - realmEntity.setBrowserSecurityHeader("key2", null); - - // then - assertThat(realmEntity.getBrowserSecurityHeaders(), allOf(aMapWithSize(1), hasEntry(equalTo("key1"), equalTo("value1")))); - } - { - // when - realmEntity.setBrowserSecurityHeader("key1", null); - - // then - // TODO: Should we set map to null if we remove last entry by calling set*(key, null) method? - assertThat(realmEntity.getBrowserSecurityHeaders(), anEmptyMap()); - } - } - - @Test - public void testAddUndefinedValuesToMapStringList() { - MapRealmEntity realmEntity = newMapRealmEntity(); - Map> attributes = new HashMap<>(); - - { - // when - attributes.put("key1", Collections.emptyList()); - realmEntity.setAttributes(attributes); - - // then - assertThat(realmEntity.getAttributes(), nullValue()); - } - { - // when - attributes.put("key1", Collections.singletonList(null)); - realmEntity.setAttributes(attributes); - - // then - assertThat(realmEntity.getAttributes(), nullValue()); - } - { - // when - attributes.put("key1", Arrays.asList(null, null, null)); - realmEntity.setAttributes(attributes); - - // then - assertThat(realmEntity.getAttributes(), nullValue()); - } - } - - @Test - public void testAddUndefinedValuesToMapStringMap() { - MapRealmEntity realmEntity = newMapRealmEntity(); - Map localizationTexts = new HashMap<>(); - - { - // when - realmEntity.setLocalizationText("en", Collections.emptyMap()); - - // then - assertThat(realmEntity.getLocalizationText("en"), nullValue()); - assertThat(realmEntity.getLocalizationTexts(), nullValue()); - } - { - // when - realmEntity.setLocalizationText("en", Collections.singletonMap("key1", null)); - - // then - assertThat(realmEntity.getLocalizationText("en"), nullValue()); - assertThat(realmEntity.getLocalizationTexts(), nullValue()); - } - { - // when - realmEntity.setLocalizationText("en", Collections.singletonMap("key1", "value1")); - - // then - assertThat(realmEntity.getLocalizationTexts(), allOf(aMapWithSize(1), hasKey("en"))); - assertThat(realmEntity.getLocalizationText("en"), allOf(aMapWithSize(1), hasEntry(equalTo("key1"), equalTo("value1")))); - } - { - // when - localizationTexts.put("key1", "value1"); - localizationTexts.put("key2", null); - realmEntity.setLocalizationText("en", localizationTexts); - - // then - assertThat(realmEntity.getLocalizationTexts(), allOf(aMapWithSize(1), hasKey("en"))); - assertThat(realmEntity.getLocalizationText("en"), allOf(aMapWithSize(1), hasEntry(equalTo("key1"), equalTo("value1")))); - } - { - // when - localizationTexts.put("key1", null); - localizationTexts.put("key2", null); - realmEntity.setLocalizationText("en", localizationTexts); - - // then - assertThat(realmEntity.getLocalizationTexts(), anEmptyMap()); - assertThat(realmEntity.getLocalizationText("en"), nullValue()); - } - } -} diff --git a/model/map/src/test/java/org/keycloak/models/map/storage/chm/CriteriaOperatorTest.java b/model/map/src/test/java/org/keycloak/models/map/storage/chm/CriteriaOperatorTest.java deleted file mode 100644 index 5b7fdaa738e..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/storage/chm/CriteriaOperatorTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022. 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.storage.chm; - -import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.Test; - -import java.util.function.Predicate; - -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; - -/** - * @author Alexander Schwartz - */ -public class CriteriaOperatorTest { - - @Test - public void shouldCompareLongs() { - Predicate predicate = CriteriaOperator.ge(new Long[]{0L}); - - assertTrue(predicate.test(1L)); - } - - @Test - public void shouldCompareInts() { - Predicate predicate = CriteriaOperator.ge(new Integer[]{0}); - - assertTrue(predicate.test(1)); - } - - @Test - public void shouldThrowAnExceptionOnIncompatibleTypes() { - Predicate predicate = CriteriaOperator.ge(new Long[]{0L}); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> predicate.test(1)); - MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.startsWith("Incomparable argument type for comparison operation")); - } - -} \ No newline at end of file diff --git a/model/map/src/test/java/org/keycloak/models/map/storage/criteria/DefaultModelCriteriaTest.java b/model/map/src/test/java/org/keycloak/models/map/storage/criteria/DefaultModelCriteriaTest.java deleted file mode 100644 index 16bb9a593e8..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/storage/criteria/DefaultModelCriteriaTest.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 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.storage.criteria; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import java.util.Arrays; -import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Test; -import static org.hamcrest.Matchers.hasToString; -import static org.hamcrest.Matchers.nullValue; -import static org.keycloak.models.ClientModel.SearchableFields.*; -import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; - -/** - * - * @author hmlnarik - */ -public class DefaultModelCriteriaTest { - - @Test - public void testSimpleCompare() { - DefaultModelCriteria v = criteria(); - assertThat(v.compare(CLIENT_ID, Operator.EQ, 3), hasToString("clientId EQ [3]")); - assertThat(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5), hasToString("(clientId EQ [4] && id EQ [5])")); - } - - @Test - public void testSimpleCompareAnd() { - DefaultModelCriteria v = criteria(); - assertThat(v.and(), hasToString("__TRUE__")); - assertThat(v.and(v.or()), hasToString("__FALSE__")); - - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 3)), hasToString("clientId EQ [3]")); - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 3), v.or()), hasToString("__FALSE__")); - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] && id EQ [5])")); - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] && id EQ [5])")); - } - - @Test - public void testSimpleCompareOr() { - DefaultModelCriteria v = criteria(); - assertThat(v.or(), hasToString("__FALSE__")); - assertThat(v.or(v.and()), hasToString("__TRUE__")); - - assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 3)), hasToString("clientId EQ [3]")); - assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 3), v.and()), hasToString("__TRUE__")); - assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] && id EQ [5])")); - assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] || id EQ [5])")); - assertThat(v.or(v.or(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5))), hasToString("(clientId EQ [4] || id EQ [5])")); - assertThat(v.and(v.or(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5))), hasToString("(clientId EQ [4] || id EQ [5])")); - } - - @Test - public void testSimpleCompareAndOr() { - DefaultModelCriteria v = criteria(); - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(ID, Operator.EQ, 5) - ), - v.not(v.not(v.compare(ATTRIBUTE, Operator.EQ, "city", "Ankh-Morpork"))) - ), hasToString("((clientId EQ [4] && id EQ [5]) || attribute EQ [city, Ankh-Morpork])")); - - DefaultModelCriteria mcb = criteria(); - assertThat(mcb.and( - mcb.compare(RoleModel.SearchableFields.REALM_ID, Operator.EQ, "realmId"), - mcb.compare(RoleModel.SearchableFields.CLIENT_ID, Operator.EQ, "clientId"), - mcb.or( - mcb.compare(RoleModel.SearchableFields.NAME, Operator.ILIKE, "%search%"), - mcb.compare(RoleModel.SearchableFields.DESCRIPTION, Operator.ILIKE, "%search%") - ) - ), hasToString("(realmId EQ [realmId] && clientId EQ [clientId] && (name ILIKE [%search%] || description ILIKE [%search%]))")); - - assertThat(mcb - .compare(RoleModel.SearchableFields.REALM_ID, Operator.EQ, "realmId") - .compare(RoleModel.SearchableFields.CLIENT_ID, Operator.EQ, "clientId") - .or( - mcb.compare(RoleModel.SearchableFields.NAME, Operator.ILIKE, "%search%"), - mcb.compare(RoleModel.SearchableFields.DESCRIPTION, Operator.ILIKE, "%search%") - ), - hasToString("(realmId EQ [realmId] && clientId EQ [clientId] && (name ILIKE [%search%] || description ILIKE [%search%]))")); - } - - @Test - public void testComplexCompareAndOr() { - DefaultModelCriteria v = criteria(); - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not(v.compare(ID, Operator.EQ, 5)) - ), - hasToString("((clientId EQ [4] && realmId EQ [aa]) || ! id EQ [5])") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not( - v.compare(ID, Operator.EQ, 5) - ).compare(ENABLED, Operator.EQ, "true") - ), - hasToString("((clientId EQ [4] && realmId EQ [aa]) || (! id EQ [5] && enabled EQ [true]))") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not( - v.not( - v.compare(ID, Operator.EQ, 5) - ).compare(ENABLED, Operator.EQ, "true") - ) - ), - hasToString("((clientId EQ [4] && realmId EQ [aa]) || ! (! id EQ [5] && enabled EQ [true]))") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not( - v.not( - v.and( - v.compare(ID, Operator.EQ, 5) - .compare(ENABLED, Operator.EQ, "true") - ) - ) - ) - ), - hasToString("((clientId EQ [4] && realmId EQ [aa]) || (id EQ [5] && enabled EQ [true]))") - ); - } - - @Test - public void testFlashingToAnotherMCB() { - DefaultModelCriteria v = criteria(); - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not(v.compare(ID, Operator.EQ, 5)) - ).flashToModelCriteriaBuilder(criteria()), - hasToString("((clientId EQ [4] && realmId EQ [aa]) || ! id EQ [5])") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not( - v.compare(ID, Operator.EQ, 5) - ).compare(ENABLED, Operator.EQ, "true") - ).flashToModelCriteriaBuilder(criteria()), - hasToString("((clientId EQ [4] && realmId EQ [aa]) || (! id EQ [5] && enabled EQ [true]))") - ); - } - - @Test - public void testCloning() { - DefaultModelCriteria v = criteria(); - - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)) - .partiallyEvaluate((field, operator, operatorArguments) -> - (field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(4)) - || (field == ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(5)) - ? true - : null - ), - hasToString("(__TRUE__ && __TRUE__)")); - - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)) - .partiallyEvaluate((field, operator, operatorArguments) -> - (field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(4)) - || (field == ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(5)) - ? true - : null - ).optimize(), - hasToString("__TRUE__")); - - assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)) - .partiallyEvaluate((field, operator, operatorArguments) -> field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(6) ? true : null), - hasToString("(clientId EQ [4] && id EQ [5])")); - } - - @Test - public void testGetFieldCriteriaSingleArgument() { - DefaultModelCriteria v = criteria(); - assertThat( - v.compare(REALM_ID, Operator.EQ, "aa") - .getSingleRestrictionArgument(REALM_ID.getName()), - hasToString("aa") - ); - - assertThat( - v.not(v.compare(REALM_ID, Operator.EQ, "aa")) - .getSingleRestrictionArgument(REALM_ID.getName()), - nullValue() - ); - - assertThat( - v.not(v.not(v.compare(REALM_ID, Operator.EQ, "aa"))) - .getSingleRestrictionArgument(REALM_ID.getName()), - hasToString("aa") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ) - ).getSingleRestrictionArgument(REALM_ID.getName()), - hasToString("aa") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.and( - v.compare(CLIENT_ID, Operator.EQ, 123), - v.compare(REALM_ID, Operator.EQ, "aa") - ) - ).getSingleRestrictionArgument(REALM_ID.getName()), - hasToString("aa") - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "bb") - ) - ).getSingleRestrictionArgument(REALM_ID.getName()), - nullValue() - ); - - assertThat(v.or( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not(v.compare(ID, Operator.EQ, 5)) - ).getSingleRestrictionArgument(REALM_ID.getName()), - nullValue() - ); - - assertThat(v.and( - v.and( - v.compare(CLIENT_ID, Operator.EQ, 4), - v.compare(REALM_ID, Operator.EQ, "aa") - ), - v.not(v.compare(ID, Operator.EQ, 5)) - ).getSingleRestrictionArgument(REALM_ID.getName()), - hasToString("aa") - ); - } -} diff --git a/model/map/src/test/java/org/keycloak/models/map/storage/tree/DefaultTreeNodeTest.java b/model/map/src/test/java/org/keycloak/models/map/storage/tree/DefaultTreeNodeTest.java deleted file mode 100644 index 6da2d164e84..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/storage/tree/DefaultTreeNodeTest.java +++ /dev/null @@ -1,558 +0,0 @@ -/* - * 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.storage.tree; - -import org.keycloak.models.map.storage.tree.TreeNode.PathOrientation; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; - -/** - * - * @author hmlnarik - */ -public class DefaultTreeNodeTest { - - private class Node extends DefaultTreeNode { - - public Node() { - super(DefaultTreeNodeTest.this.treeProperties); - } - - public Node(String id) { - super(DefaultTreeNodeTest.this.treeProperties); - setId(id); - } - - public Node(Node parent, String id) { - super(DefaultTreeNodeTest.this.treeProperties); - setId(id); - setParent(parent); - } - - @Override - public String getLabel() { - return this.getId() == null ? "Node:" + System.identityHashCode(this) : this.getId(); - } - } - - private static final String KEY_1 = "key1"; - private static final String VALUE_1 = "value"; - private static final String KEY_2 = "key2"; - private static final Date VALUE_2 = new Date(); - private static final String KEY_3 = "key3"; - private static final Integer VALUE_3 = 12345; - - public Map treeProperties = new HashMap<>(); - - { - treeProperties.put(KEY_1, VALUE_1); - treeProperties.put(KEY_2, VALUE_2); - } - - @Test - public void testSingleNodeTree() { - Node root = new Node(); - root.setNodeProperty(KEY_1, VALUE_1); - root.setEdgeProperty(KEY_2, VALUE_2); - - assertThat(root.getParent(), is(Optional.empty())); - assertThat(root.getChildren(), empty()); - - assertNodeProperty(root, KEY_1, VALUE_1); - assertNodeProperty(root, KEY_2, null); - assertEdgeProperty(root, KEY_1, null); - assertEdgeProperty(root, KEY_2, VALUE_2); - - assertTreeProperties(root); - } - - @Test - public void testSimpleTwoNodeTree() { - Node root = new Node(); - Node child = new Node(); - root.setNodeProperty(KEY_1, VALUE_1); - - child.setParent(root); - child.setId("my-id"); - child.setEdgeProperty(KEY_2, VALUE_2); - - // check parent-child relationships - assertThat(root.getParent(), is(Optional.empty())); - assertThat(root.getChildren(), hasSize(1)); - - assertThat(child.getParent(), is(Optional.of(root))); - assertThat(child.getChildren(), empty()); - - // check properties - assertThat(root.getNodeProperties().keySet(), hasSize(1)); - assertThat(root.getEdgeProperties().keySet(), empty()); - assertThat(child.getNodeProperties().keySet(), empty()); - assertThat(child.getEdgeProperties().keySet(), hasSize(1)); - assertTreeProperties(root); - assertTreeProperties(child); - } - - @Test - public void testSimpleTwoNodeTreeSwapped() { - Node root = new Node(); - Node child = new Node(); - child.setParent(root); - child.setId("my-id"); - - // Now swap the roles - root.setParent(child); - - // check parent-child relationships - assertThat(child.getParent(), is(Optional.empty())); - assertThat(child.getChildren(), hasSize(1)); - - assertThat(root.getParent(), is(Optional.of(child))); - assertThat(root.getChildren(), empty()); - - // check properties have not changed - root.setNodeProperty(KEY_1, VALUE_1); - child.setEdgeProperty(KEY_2, VALUE_2); - assertThat(root.getNodeProperties().keySet(), hasSize(1)); - assertThat(root.getEdgeProperties().keySet(), empty()); - assertThat(child.getNodeProperties().keySet(), empty()); - assertThat(child.getEdgeProperties().keySet(), hasSize(1)); - assertTreeProperties(root); - assertTreeProperties(child); - } - - @Test - public void testStructureLinearThreeNodeSwapped() { - Node level1 = new Node(); - Node level2 = new Node(); - Node level3 = new Node(); - - level2.setParent(level1); - level3.setParent(level2); - - // check parent-child relationships - assertThat(level1.getParent(), is(Optional.empty())); - assertThat(level1.getChildren(), containsInAnyOrder(level2)); - assertThat(level2.getParent(), is(Optional.of(level1))); - assertThat(level2.getChildren(), containsInAnyOrder(level3)); - assertThat(level3.getParent(), is(Optional.of(level2))); - assertThat(level3.getChildren(), empty()); - - // Swap nodes - level1.setParent(level3); - - // check parent-child relationships - assertThat(level3.getParent(), is(Optional.empty())); - assertThat(level3.getChildren(), containsInAnyOrder(level1)); - assertThat(level1.getParent(), is(Optional.of(level3))); - assertThat(level1.getChildren(), containsInAnyOrder(level2)); - assertThat(level2.getParent(), is(Optional.of(level1))); - assertThat(level2.getChildren(), empty()); - } - - @Test - public void testStructureAThreeNodeSwapped() { - Node level1 = new Node(); - Node level21 = new Node(); - Node level22 = new Node(); - Node level23 = new Node(); - - level21.setParent(level1); - level22.setParent(level1); - level23.setParent(level1); - - // check parent-child relationships - assertThat(level1.getParent(), is(Optional.empty())); - assertThat(level1.getChildren(), containsInAnyOrder(level21, level22, level23)); - assertThat(level21.getParent(), is(Optional.of(level1))); - assertThat(level21.getChildren(), empty()); - assertThat(level22.getParent(), is(Optional.of(level1))); - assertThat(level22.getChildren(), empty()); - assertThat(level23.getParent(), is(Optional.of(level1))); - assertThat(level23.getChildren(), empty()); - - // Change parents - level1.setParent(level22); - - // check parent-child relationships - assertThat(level22.getParent(), is(Optional.empty())); - assertThat(level22.getChildren(), containsInAnyOrder(level1)); - assertThat(level1.getParent(), is(Optional.of(level22))); - assertThat(level1.getChildren(), containsInAnyOrder(level21, level23)); - assertThat(level21.getParent(), is(Optional.of(level1))); - assertThat(level21.getChildren(), empty()); - assertThat(level23.getParent(), is(Optional.of(level1))); - assertThat(level23.getChildren(), empty()); - - // Change parents - level21.setParent(level22); - - // check parent-child relationships - assertThat(level22.getParent(), is(Optional.empty())); - assertThat(level22.getChildren(), containsInAnyOrder(level1, level21)); - assertThat(level1.getParent(), is(Optional.of(level22))); - assertThat(level1.getChildren(), containsInAnyOrder(level23)); - assertThat(level21.getParent(), is(Optional.of(level22))); - assertThat(level21.getChildren(), empty()); - assertThat(level23.getParent(), is(Optional.of(level1))); - assertThat(level23.getChildren(), empty()); - - // Change parents - level21.setParent(null); - - // check parent-child relationships - assertThat(level22.getParent(), is(Optional.empty())); - assertThat(level22.getChildren(), containsInAnyOrder(level1)); - assertThat(level1.getParent(), is(Optional.of(level22))); - assertThat(level1.getChildren(), containsInAnyOrder(level23)); - assertThat(level21.getParent(), is(Optional.empty())); - assertThat(level21.getChildren(), empty()); - assertThat(level23.getParent(), is(Optional.of(level1))); - assertThat(level23.getChildren(), empty()); - } - - @Test - public void testChangeId() { - Node root = new Node(); - Node child1 = new Node(); - child1.setParent(root); - child1.setId("my-id1"); - Node child2 = new Node(); - child2.setParent(root); - child2.setId("my-id2"); - - // check parent-child relationships - assertThat(root.getChild("my-id1"), is(Optional.of(child1))); - assertThat(root.getChild("my-id2"), is(Optional.of(child2))); - - child1.setId("my-id3"); - assertThat(root.getChild("my-id1"), is(Optional.empty())); - assertThat(root.getChild("my-id3"), is(Optional.of(child1))); - } - - @Test - public void testRemoveChildDirectly() { - Node root = new Node(); - Node child1 = new Node(); - child1.setParent(root); - child1.setId("my-id1"); - Node child2 = new Node(); - child2.setParent(root); - child2.setId("my-id2"); - - // check parent-child relationships - assertThat(root.getChild("my-id1"), is(Optional.of(child1))); - assertThat(root.getChild("my-id2"), is(Optional.of(child2))); - - assertThat(root.removeChild(child1), is(Optional.of(child1))); - - assertThat(root.getChildren(), containsInAnyOrder(child2)); - assertThat(child1.getParent(), is(Optional.empty())); - - assertThat(root.removeChild(child1), is(Optional.empty())); - - // try to remove it once again - assertThat(root.getChildren(), containsInAnyOrder(child2)); - assertThat(child1.getParent(), is(Optional.empty())); - } - - @Test - public void testRemoveChildViaPredicate() { - Node root = new Node(); - Node child1 = new Node(); - child1.setParent(root); - child1.setId("my-id1"); - Node child2 = new Node(); - child2.setParent(root); - child2.setId("my-id2"); - Node child3 = new Node(); - child3.setParent(root); - child3.setId("my-id3"); - - // check removals - assertThat(root.removeChild(node -> "my-id1".equals(node.getId())), is(1)); - - assertThat(root.getChildren(), containsInAnyOrder(child2, child3)); - assertThat(child1.getParent(), is(Optional.empty())); - assertThat(child2.getParent(), is(Optional.of(root))); - assertThat(child3.getParent(), is(Optional.of(root))); - - assertThat(root.removeChild(node -> true), is(2)); - - assertThat(root.getChildren(), empty()); - assertThat(child1.getParent(), is(Optional.empty())); - assertThat(child2.getParent(), is(Optional.empty())); - assertThat(child3.getParent(), is(Optional.empty())); - } - - @Test - public void testRemoveChild() { - Node root = new Node(); - Node child1 = new Node(); - child1.setParent(root); - child1.setId("my-id1"); - Node child2 = new Node(); - child2.setParent(root); - child2.setId("my-id2"); - - // check parent-child relationships - assertThat(root.getChild("my-id1"), is(Optional.of(child1))); - assertThat(root.getChild("my-id2"), is(Optional.of(child2))); - - root.removeChild(child1); - - assertThat(root.getChildren(), containsInAnyOrder(child2)); - assertThat(child1.getParent(), is(Optional.empty())); - } - - @Test - public void testDfs() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - - List res = new LinkedList<>(); - assertThat(root.findFirstDfs(n -> { - res.add(n); - return false; - }), is(Optional.empty())); - assertThat(res, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123)); - - res.clear(); - assertThat(root.findFirstDfs(n -> { - res.add(n); - return n == child12; - }), is(Optional.of(child12))); - assertThat(res, contains(root, child11, child111, child112, child1121, child12)); - } - - @Test - public void testDfsBottommost() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child13 = new Node(root, "1.3"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - Node child131 = new Node(child13, "1.3.1"); - Node child132 = new Node(child13, "1.3.2"); - - List res = new LinkedList<>(); - assertThat(root.findFirstBottommostDfs(n -> { - res.add(n); - return false; - }), is(Optional.empty())); - assertThat(res, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123, child13, child131, child132)); - - res.clear(); - assertThat(root.findFirstBottommostDfs(n -> { - res.add(n); - return n == child12; - }), is(Optional.of(child12))); - assertThat(res, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123)); - - res.clear(); - assertThat(root.findFirstBottommostDfs(n -> { - res.add(n); - return n.getId().startsWith("1.1.2"); - }), is(Optional.of(child1121))); - assertThat(res, contains(root, child11, child111, child112, child1121)); - } - - @Test - public void testBfs() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - - List res = new LinkedList<>(); - assertThat(root.findFirstBfs(n -> { - res.add(n); - return false; - }), is(Optional.empty())); - assertThat(res, contains(root, child11, child12, child111, child112, child121, child122, child123, child1121)); - - res.clear(); - assertThat(root.findFirstBfs(n -> { - res.add(n); - return n == child12; - }), is(Optional.of(child12))); - assertThat(res, contains(root, child11, child12)); - } - - @Test - public void testWalkBfs() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - - List res = new LinkedList<>(); - root.walkBfs(res::add); - assertThat(res, contains(root, child11, child12, child111, child112, child121, child122, child123, child1121)); - } - - @Test - public void testWalkDfs() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - - List uponEntry = new LinkedList<>(); - List afterChildren = new LinkedList<>(); - root.walkDfs(uponEntry::add, afterChildren::add); - assertThat(uponEntry, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123)); - assertThat(afterChildren, contains(child111, child1121, child112, child11, child121, child122, child123, child12, root)); - } - - @Test - public void testForEachParent() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - - List res = new LinkedList<>(); - res.clear(); - root.forEachParent(res::add); - assertThat(res, empty()); - - res.clear(); - child1121.forEachParent(res::add); - assertThat(res, contains(child112, child11, root)); - - res.clear(); - child123.forEachParent(res::add); - assertThat(res, contains(child12, root)); - } - - @Test - public void testPathToRoot() { - Node root = new Node("1"); - Node child11 = new Node(root, "1.1"); - Node child12 = new Node(root, "1.2"); - Node child111 = new Node(child11, "1.1.1"); - Node child112 = new Node(child11, "1.1.2"); - Node child121 = new Node(child12, "1.2.1"); - Node child122 = new Node(child12, "1.2.2"); - Node child123 = new Node(child12, "1.2.3"); - Node child1121 = new Node(child112, "1.1.2.1"); - - assertThat(child1121.getPathToRoot(PathOrientation.TOP_FIRST), contains(root, child11, child112, child1121)); - assertThat(child123.getPathToRoot(PathOrientation.TOP_FIRST), contains(root, child12, child123)); - assertThat(root.getPathToRoot(PathOrientation.TOP_FIRST), contains(root)); - - assertThat(child1121.getPathToRoot(PathOrientation.BOTTOM_FIRST), contains(child1121, child112, child11, root)); - assertThat(child123.getPathToRoot(PathOrientation.BOTTOM_FIRST), contains(child123, child12, root)); - assertThat(root.getPathToRoot(PathOrientation.BOTTOM_FIRST), contains(root)); - } - - @Test - public void testToStringStackOverflow() { - Node n = new Node("1"); - n.setNodeProperty("prop", n); - assertThat(n.toString().length(), lessThan(255)); - } - - private void assertTreeProperties(Node node) { - assertThat(node.getTreeProperty(KEY_1, String.class), notNullValue()); - assertThat(node.getTreeProperty(KEY_1, Date.class), notNullValue()); - - assertThat(node.getTreeProperty(KEY_1, String.class), is(Optional.of(VALUE_1))); - assertThat(node.getTreeProperty(KEY_1, Date.class), is(Optional.empty())); - - assertThat(node.getTreeProperty(KEY_2, String.class), is(Optional.empty())); - assertThat(node.getTreeProperty(KEY_2, Date.class), is(Optional.of(VALUE_2))); - - assertThat(node.getTreeProperties().size(), is(2)); - - treeProperties.put(KEY_3, VALUE_3); - assertThat(node.getTreeProperties().size(), is(3)); - assertThat(node.getTreeProperty(KEY_3, String.class), is(Optional.empty())); - assertThat(node.getTreeProperty(KEY_3, Integer.class), is(Optional.of(VALUE_3))); - - treeProperties.remove(KEY_3); - assertThat(node.getTreeProperties().size(), is(2)); - assertThat(node.getTreeProperties(), not(hasKey(KEY_3))); - } - - private void assertNodeProperty(Node node, String key, Object value) { - if (value != null) { - assertThat(node.getNodeProperty(key, value.getClass()), is(Optional.of(value))); - assertThat(node.getNodeProperty(key, Object.class), is(Optional.of(value))); - assertThat(node.getNodeProperty(key, Throwable.class), is(Optional.empty())); - } else { - assertThat(node.getNodeProperty(key, Object.class), is(Optional.empty())); - } - } - - private void assertEdgeProperty(Node node, String key, Object value) { - if (value != null) { - assertThat(node.getEdgeProperty(key, value.getClass()), is(Optional.of(value))); - assertThat(node.getEdgeProperty(key, Object.class), is(Optional.of(value))); - assertThat(node.getEdgeProperty(key, Throwable.class), is(Optional.empty())); - } else { - assertThat(node.getEdgeProperty(key, Object.class), is(Optional.empty())); - } - } -} diff --git a/model/map/src/test/java/org/keycloak/models/map/storage/tree/TreeStorageNodePrescriptionTest.java b/model/map/src/test/java/org/keycloak/models/map/storage/tree/TreeStorageNodePrescriptionTest.java deleted file mode 100644 index 5ab3e7c7623..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/storage/tree/TreeStorageNodePrescriptionTest.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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.storage.tree; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityFields; -import org.keycloak.models.map.client.MapClientEntityFields; -import org.keycloak.models.map.realm.MapRealmEntity; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.tree.TreeStorageNodePrescription.FieldContainedStatus; -import java.util.Arrays; -import java.util.Collection; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.sameInstance; - -/** - * - * @author hmlnarik - */ -public class TreeStorageNodePrescriptionTest { - - @Test - public void testEmpty() { - TreeStorageNodePrescription n = new TreeStorageNodePrescription(null); - TreeStorageNodePrescription c1 = n.forEntityClass(MapClientEntity.class); - TreeStorageNodePrescription c2 = n.forEntityClass(MapClientEntity.class); - TreeStorageNodePrescription r1 = n.forEntityClass(MapRealmEntity.class); - - assertThat(c1, sameInstance(c2)); - assertThat(c1, not(sameInstance(r1))); - assertThat(c1.getNodeProperties().entrySet(), empty()); - assertThat(c1.getEdgeProperties().entrySet(), empty()); - assertThat(c1.getTreeProperties().size(), is(1)); - - assertThat(c1.getTreeProperties(), hasEntry(TreeProperties.MODEL_CLASS, ClientModel.class)); - assertThat(r1.getTreeProperties(), hasEntry(TreeProperties.MODEL_CLASS, RealmModel.class)); - } - - @Test - public void testTreePropertyProjection() { - Map treeProperties = new HashMap<>(); - treeProperties.put("prop[" + ModelEntityUtil.getModelName(ClientModel.class) + "]", "propClientValue"); - treeProperties.put("prop[" + ModelEntityUtil.getModelName(RealmModel.class) + "]", "propRealmValue"); - treeProperties.put("propRealmOnly[" + ModelEntityUtil.getModelName(RealmModel.class) + "]", "propRealmOnlyValue"); - treeProperties.put("propBoth", "propBothValue"); - - Map nodeProperties = new HashMap<>(); - nodeProperties.put("nprop[" + ModelEntityUtil.getModelName(ClientModel.class) + "]", "propClientValue"); - nodeProperties.put("nprop[" + ModelEntityUtil.getModelName(RealmModel.class) + "]", "propRealmValue"); - nodeProperties.put("npropRealmOnly[" + ModelEntityUtil.getModelName(RealmModel.class) + "]", "propRealmOnlyValue"); - nodeProperties.put("npropBoth", "propBothValue"); - - Map edgeProperties = new HashMap<>(); - edgeProperties.put("eprop[" + ModelEntityUtil.getModelName(ClientModel.class) + "]", "propClientValue"); - edgeProperties.put("eprop[" + ModelEntityUtil.getModelName(RealmModel.class) + "]", "propRealmValue"); - edgeProperties.put("epropRealmOnly[" + ModelEntityUtil.getModelName(RealmModel.class) + "]", "propRealmOnlyValue"); - edgeProperties.put("epropBoth", "propBothValue"); - - TreeStorageNodePrescription n = new TreeStorageNodePrescription(nodeProperties, edgeProperties, treeProperties); - TreeStorageNodePrescription c1 = n.forEntityClass(MapClientEntity.class); - TreeStorageNodePrescription r1 = n.forEntityClass(MapRealmEntity.class); - - assertThat(c1.getTreeProperties(), hasEntry(TreeProperties.MODEL_CLASS, ClientModel.class)); - assertThat(c1.getTreeProperties(), hasEntry("prop", "propClientValue")); - assertThat(c1.getTreeProperties(), hasEntry("propBoth", "propBothValue")); - assertThat(c1.getTreeProperties().size(), is(3)); - assertThat(c1.getNodeProperties(), hasEntry("nprop", "propClientValue")); - assertThat(c1.getNodeProperties(), hasEntry("npropBoth", "propBothValue")); - assertThat(c1.getNodeProperties().size(), is(2)); - assertThat(c1.getEdgeProperties(), hasEntry("eprop", "propClientValue")); - assertThat(c1.getEdgeProperties(), hasEntry("epropBoth", "propBothValue")); - assertThat(c1.getEdgeProperties().size(), is(2)); - - assertThat(r1.getTreeProperties(), hasEntry(TreeProperties.MODEL_CLASS, RealmModel.class)); - assertThat(r1.getTreeProperties(), hasEntry("prop", "propRealmValue")); - assertThat(r1.getTreeProperties(), hasEntry("propRealmOnly", "propRealmOnlyValue")); - assertThat(r1.getTreeProperties(), hasEntry("propBoth", "propBothValue")); - assertThat(r1.getTreeProperties().size(), is(4)); - } - - /** - * Test a node that has neither PRIMARY_SOURCE_FOR nor PRIMARY_SOURCE_FOR_EXCLUDED set. - *

- * Represents e.g. a node in the tree that stores all fields. - */ - @Test - public void testPrimarySourceForBasicUnset() { - Map nodeProperties = new HashMap<>(); - TreeStorageNodePrescription n = new TreeStorageNodePrescription(nodeProperties, null, null); - - for (MapClientEntityFields field : MapClientEntityFields.values()) { - assertThat("Field " + field + " has primary source in this node", n.isPrimarySourceFor(field, null), is(FieldContainedStatus.FULLY)); - } - } - - /** - * Test a node that has PRIMARY_SOURCE_FOR set to all fields with no specialization (i.e. {@code null}), - * and no PRIMARY_SOURCE_FOR_EXCLUDED is set. - *

- * Represents e.g. a node in the tree that stores all fields. - */ - @Test - public void testPrimarySourceForBasicSet() { - Map nodeProperties = new HashMap<>(); - EnumMap> primarySourceFor = new EnumMap<>(MapClientEntityFields.class); - for (MapClientEntityFields field : MapClientEntityFields.values()) { - primarySourceFor.put(field, null); - } - nodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR, primarySourceFor); - TreeStorageNodePrescription n = new TreeStorageNodePrescription(nodeProperties, null, null); - - for (MapClientEntityFields field : MapClientEntityFields.values()) { - assertThat("Field " + field + " has primary source in this node", n.isPrimarySourceFor(field, null), is(FieldContainedStatus.FULLY)); - } - } - - /** - * Test a node that has PRIMARY_SOURCE_FOR set to only ID field with no specialization (i.e. {@code null}), - * and no PRIMARY_SOURCE_FOR_EXCLUDED is set. - *

- * Represents e.g. a node in the tree that stores only ID (maintains existence check of an object) - */ - @Test - public void testPrimarySourceForBasicSetId() { - Map nodeProperties = new HashMap<>(); - EnumMap> primarySourceFor = new EnumMap<>(MapClientEntityFields.class); - nodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR, primarySourceFor); - primarySourceFor.put(MapClientEntityFields.ID, null); - TreeStorageNodePrescription n = new TreeStorageNodePrescription(nodeProperties, null, null); - - for (MapClientEntityFields field : MapClientEntityFields.values()) { - assertThat(n.isPrimarySourceFor(field, null), - is(field == MapClientEntityFields.ID ? FieldContainedStatus.FULLY : FieldContainedStatus.NOT_CONTAINED)); - } - } - - /** - * Test a node that has no PRIMARY_SOURCE_FOR set, - * and PRIMARY_SOURCE_FOR_EXCLUDED is set to ATTRIBUTES field with no specialization (i.e. {@code null}). - *

- * Represents e.g. a node in the tree that stores all attributes apart from attributes - */ - @Test - public void testPrimarySourceForWithExcluded() { - Map nodeProperties = new HashMap<>(); - EnumMap> primarySourceForExcluded = new EnumMap<>(MapClientEntityFields.class); - nodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, primarySourceForExcluded); - primarySourceForExcluded.put(MapClientEntityFields.ATTRIBUTES, null); - - // node is primary for all fields apart from all attributes - TreeStorageNodePrescription n = new TreeStorageNodePrescription(nodeProperties, null, null); - - for (MapClientEntityFields field : MapClientEntityFields.values()) { - assertThat(n.isPrimarySourceFor(field, null), - is(field == MapClientEntityFields.ATTRIBUTES ? FieldContainedStatus.NOT_CONTAINED: FieldContainedStatus.FULLY)); - } - } - - /** - * Test a node that has PRIMARY_SOURCE_FOR set to ATTRIBUTES field with no specialization (i.e. {@code null}), - * and PRIMARY_SOURCE_FOR_EXCLUDED is set to ATTRIBUTES field specialization to "attr1" and "attr2". - *

- * Represents e.g. a node in the tree that acts as a supplementary store for all attributes apart from "attr1" and "attr2" - */ - @Test - public void testPrimarySourceForWithExcludedTwoAttributes() { - Map nodeProperties = new HashMap<>(); - EnumMap> primarySourceFor = new EnumMap<>(MapClientEntityFields.class); - nodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR, primarySourceFor); - primarySourceFor.put(MapClientEntityFields.ATTRIBUTES, null); - EnumMap> primarySourceForExcluded = new EnumMap<>(MapClientEntityFields.class); - nodeProperties.put(NodeProperties.PRIMARY_SOURCE_FOR_EXCLUDED, primarySourceForExcluded); - primarySourceForExcluded.put(MapClientEntityFields.ATTRIBUTES, Arrays.asList("attr1", "attr2")); - - // node is primary for all attributes apart from "attr1" and "attr2" - TreeStorageNodePrescription n = new TreeStorageNodePrescription(nodeProperties, null, null); - - assertThat("Field ID has NOT primary source in this node", n.isPrimarySourceFor(MapClientEntityFields.ID, null), is(FieldContainedStatus.NOT_CONTAINED)); - assertThat("Attribute attr1 has NOT primary source in this node", n.isPrimarySourceFor(MapClientEntityFields.ATTRIBUTES, "attr1"), is(FieldContainedStatus.NOT_CONTAINED)); - assertThat("Attribute attr2 has NOT primary source in this node", n.isPrimarySourceFor(MapClientEntityFields.ATTRIBUTES, "attr2"), is(FieldContainedStatus.NOT_CONTAINED)); - assertThat("Attribute attr3 has primary source in this node", n.isPrimarySourceFor(MapClientEntityFields.ATTRIBUTES, "attr3"), is(FieldContainedStatus.FULLY)); - assertThat("Attributes have primary source in this node and other source in some other nodes", n.isPrimarySourceFor(MapClientEntityFields.ATTRIBUTES, null), is(FieldContainedStatus.PARTIALLY)); - } - -} diff --git a/model/map/src/test/java/org/keycloak/models/map/user/MapUserEntityImplCredentialsOrderTest.java b/model/map/src/test/java/org/keycloak/models/map/user/MapUserEntityImplCredentialsOrderTest.java deleted file mode 100644 index ec5502fc5c4..00000000000 --- a/model/map/src/test/java/org/keycloak/models/map/user/MapUserEntityImplCredentialsOrderTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2022 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.user; - -import org.junit.Before; -import org.junit.Test; -import org.hamcrest.Matchers; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import org.keycloak.models.map.common.DeepCloner; - -import java.util.List; -import java.util.stream.Collectors; - -public class MapUserEntityImplCredentialsOrderTest { - - private MapUserEntity user; - private final static DeepCloner CLONER = new DeepCloner.Builder() - .constructor(MapUserCredentialEntityImpl.class, MapUserCredentialEntityImpl::new) - .build(); - - @Before - public void init() { - user = new MapUserEntityImpl(CLONER); - - for (int i = 1; i <= 5; i++) { - MapUserCredentialEntity credentialModel = DeepCloner.DUMB_CLONER.newInstance(MapUserCredentialEntity.class); - credentialModel.setId(Integer.toString(i)); - - user.addCredential(credentialModel); - } - - user.clearUpdatedFlag(); - } - - private void assertOrder(Integer... ids) { - List currentList = user.getCredentials().stream().map(entity -> Integer.valueOf(entity.getId())).collect(Collectors.toList()); - assertThat(currentList, Matchers.contains(ids)); - } - - @Test - public void testCorrectOrder() { - assertOrder(1, 2, 3, 4, 5); - } - - @Test - public void testMoveToZero() { - user.moveCredential("3", null); - assertOrder(3, 1, 2, 4, 5); - assertThat(user.isUpdated(), is(true)); - } - - @Test - public void testMoveBack() { - user.moveCredential("4", "1"); - assertOrder(1, 4, 2, 3, 5); - assertThat(user.isUpdated(), is(true)); - } - - @Test - public void testMoveForward() { - user.moveCredential("2", "4"); - assertOrder(1, 3, 4, 2, 5); - assertThat(user.isUpdated(), is(true)); - } - - @Test - public void testSamePosition() { - user.moveCredential("2", "1"); - assertOrder(1, 2, 3, 4, 5); - assertThat(user.isUpdated(), is(false)); - } - - @Test - public void testSamePositionZero() { - user.moveCredential("1", null); - assertOrder(1, 2, 3, 4, 5); - assertThat(user.isUpdated(), is(false)); - } -} \ No newline at end of file diff --git a/model/pom.xml b/model/pom.xml index c110ae4e537..c6a428efc10 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -35,12 +35,6 @@ legacy-private legacy-services jpa - map-jpa infinispan - map - build-processor - map-hot-rod - map-ldap - map-file diff --git a/quarkus/config-api/pom.xml b/quarkus/config-api/pom.xml index 5b4e814cc57..172259fd9c9 100755 --- a/quarkus/config-api/pom.xml +++ b/quarkus/config-api/pom.xml @@ -41,17 +41,6 @@ infinispan-commons-jakarta ${infinispan.version} - - org.keycloak - keycloak-model-map-hot-rod - ${project.version} - - - * - * - - - diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/StorageOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/StorageOptions.java index 0887346e179..21c8004f781 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/StorageOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/StorageOptions.java @@ -19,10 +19,8 @@ package org.keycloak.config; import static java.util.function.Predicate.not; -import org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors; -import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; - import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -351,7 +349,7 @@ public class StorageOptions { } private static List getExpectedCacheNames() { - return Stream.concat(Stream.of("all"), AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getCacheName).distinct()).collect(Collectors.toList()); + return Collections.emptyList(); } public static Optional getDatabaseVendor(String databaseKind) { diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index b1c2acaedda..33f3e173e1c 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -74,7 +74,6 @@ import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.JpaConnectionSpi; import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory; import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider; -import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory; import org.keycloak.protocol.ProtocolMapperSpi; import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper; @@ -183,8 +182,7 @@ class KeycloakProcessor { FilesPlainTextVaultProviderFactory.class, BlacklistPasswordPolicyProviderFactory.class, ClasspathThemeResourceProviderFactory.class, - JarThemeProviderFactory.class, - JpaMapStorageProviderFactory.class); + JarThemeProviderFactory.class); static { DEPLOYEABLE_SCRIPT_PROVIDERS.put(AUTHENTICATORS, KeycloakProcessor::registerScriptAuthenticator); diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/LiquibaseProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/LiquibaseProcessor.java index 0b299ee2702..449a1bbfbe5 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/LiquibaseProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/LiquibaseProcessor.java @@ -28,7 +28,6 @@ import liquibase.parser.ChangeLogParser; import liquibase.parser.core.xml.XMLChangeLogSAXParser; import liquibase.servicelocator.LiquibaseService; import liquibase.sqlgenerator.SqlGenerator; -import org.keycloak.models.map.storage.jpa.liquibase.lockservice.KeycloakLockService; import org.keycloak.quarkus.runtime.KeycloakRecorder; import static org.keycloak.config.StorageOptions.STORAGE; @@ -80,11 +79,7 @@ class LiquibaseProcessor { } } - if (StorageOptions.StorageType.jpa.name().equals(getOptionalValue(NS_KEYCLOAK_PREFIX.concat(STORAGE.getKey())).orElse(null))) { - services.put(LockService.class.getName(), Collections.singletonList(KeycloakLockService.class.getName())); - } else { - services.put(LockService.class.getName(), Collections.singletonList(DummyLockService.class.getName())); - } + services.put(LockService.class.getName(), Collections.singletonList(DummyLockService.class.getName())); services.put(ChangeLogParser.class.getName(), Collections.singletonList(XMLChangeLogSAXParser.class.getName())); recorder.configureLiquibase(services); diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index 5177c0ab870..5b7586de01d 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -317,50 +317,6 @@ - - org.keycloak - keycloak-model-map - ${project.version} - - - * - * - - - - - org.keycloak - keycloak-model-map-jpa - ${project.version} - - - * - * - - - - - org.keycloak - keycloak-model-map-hot-rod - ${project.version} - - - * - * - - - - - org.keycloak - keycloak-model-map-file - ${project.version} - - - * - * - - - org.keycloak keycloak-account-ui diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/IgnoredArtifacts.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/IgnoredArtifacts.java index 8edd17a51f5..dab1100fadf 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/IgnoredArtifacts.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/IgnoredArtifacts.java @@ -17,13 +17,10 @@ package org.keycloak.quarkus.runtime.configuration; import org.keycloak.common.Profile; -import org.keycloak.config.StorageOptions; import java.util.HashSet; -import java.util.Optional; import java.util.Set; -import static java.util.Collections.emptySet; import static org.keycloak.quarkus.runtime.Environment.getCurrentOrCreateFeatureProfile; /** @@ -34,7 +31,6 @@ public class IgnoredArtifacts { public static Set getDefaultIgnoredArtifacts() { return new Builder() .append(fips()) - .append(storage()) .build(); } @@ -60,21 +56,6 @@ public class IgnoredArtifacts { return isFipsEnabled ? FIPS_ENABLED : FIPS_DISABLED; } - // Map Store - public static final Set MAP_STORE = Set.of( - "org.keycloak:keycloak-model-map-jpa", - "org.keycloak:keycloak-model-map-hot-rod", - "org.keycloak:keycloak-model-map", - "org.keycloak:keycloak-model-map-file" - ); - - private static Set storage() { - Optional storage = Configuration.getOptionalValue( - MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + StorageOptions.STORAGE.getKey()); - - return storage.isEmpty() ? MAP_STORE : emptySet(); - } - /** * Builder for artifacts aggregation */ diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java deleted file mode 100644 index 09410e49a34..00000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2022 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.quarkus.runtime.storage.database.jpa; - -import static org.keycloak.config.StorageOptions.STORAGE; -import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; - -import java.lang.annotation.Annotation; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Optional; -import jakarta.enterprise.inject.Instance; -import jakarta.persistence.EntityManagerFactory; -import org.hibernate.internal.SessionFactoryImpl; -import org.keycloak.config.StorageOptions.StorageType; -import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; -import org.keycloak.quarkus.runtime.configuration.Configuration; - -import io.quarkus.arc.Arc; -import io.quarkus.hibernate.orm.PersistenceUnit; - -public class QuarkusJpaMapStorageProviderFactory extends JpaMapStorageProviderFactory { - - @Override - public String getId() { - return StorageType.jpa.getProvider(); - } - - @Override - protected EntityManagerFactory createEntityManagerFactory() { - Instance instance = Arc.container().select(EntityManagerFactory.class); - - if (instance.isResolvable()) { - return instance.get(); - } - - return getEntityManagerFactory("keycloak-default").orElseThrow(() -> new IllegalStateException("Failed to resolve the default entity manager factory")); - } - - @Override - protected Connection getConnection() { - SessionFactoryImpl entityManagerFactory = getEntityManagerFactory().unwrap(SessionFactoryImpl.class); - try { - return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection(); - } catch (SQLException cause) { - throw new RuntimeException("Failed to obtain JDBC connection", cause); - } - } - - private Optional getEntityManagerFactory(String unitName) { - Instance instance = Arc.container().select(EntityManagerFactory.class, new PersistenceUnit() { - - @Override - public Class annotationType() { - return PersistenceUnit.class; - } - - @Override - public String value() { - return unitName; - } - }); - - if (instance.isResolvable()) { - return Optional.of(instance.get()); - } - - return Optional.empty(); - } - - @Override - public boolean isSupported() { - return StorageType.jpa.name().equals(Configuration.getOptionalValue(NS_KEYCLOAK_PREFIX + STORAGE.getKey()).orElse(null)); - } -} diff --git a/quarkus/runtime/src/main/resources/META-INF/keycloak.conf b/quarkus/runtime/src/main/resources/META-INF/keycloak.conf index 6fa8f0f41c7..7df84f6363e 100644 --- a/quarkus/runtime/src/main/resources/META-INF/keycloak.conf +++ b/quarkus/runtime/src/main/resources/META-INF/keycloak.conf @@ -20,9 +20,3 @@ db=dev-file #logging defaults log-console-output=default log-file=${kc.home.dir:default}${file.separator}data${file.separator}log${file.separator}keycloak.log - -# Storage defaults -spi-map-storage-concurrenthashmap-dir=${kc.home.dir:default}${file.separator}data${file.separator}chm -spi-map-storage-concurrenthashmap-key-type-single-use-objects=string -spi-map-storage-concurrenthashmap-key-type-realms=string -spi-map-storage-concurrenthashmap-key-type-authz-resource-servers=string \ No newline at end of file diff --git a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory b/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory deleted file mode 100644 index 6b36fe6e5b6..00000000000 --- a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.models.map.storage.MapStorageProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2022 Red Hat, Inc. and/or its affiliates -# and other contributors as indicated by the @author tags. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaMapStorageProviderFactory \ No newline at end of file diff --git a/quarkus/runtime/src/main/resources/META-INF/services/quarkus.properties b/quarkus/runtime/src/main/resources/META-INF/services/quarkus.properties index 357bf96e8cf..637fe419c5c 100644 --- a/quarkus/runtime/src/main/resources/META-INF/services/quarkus.properties +++ b/quarkus/runtime/src/main/resources/META-INF/services/quarkus.properties @@ -21,7 +21,6 @@ quarkus.log.min-level=TRACE quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN quarkus.log.category."org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup".level=WARN -quarkus.log.category."org.infinispan.client.hotrod.impl.query.RemoteQuery".level=ERROR #jndi needed for LDAP lookups quarkus.naming.enable-jndi=true diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java index 161936e8070..a32741dbc55 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java @@ -44,7 +44,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.keycloak.Config; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; @@ -135,11 +134,6 @@ public class ConfigurationTest { assertEquals("debug", createConfig().getRawValue("kc.log-level")); } - @Test - public void testSanitizeKey() { - assertEquals("string", initConfig("map-storage", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID).get("keyType.realms")); - } - @Test public void testEnvVarAvailableFromPropertyNames() { putEnvVar("KC_VAULT_DIR", "/foo/bar"); @@ -512,30 +506,6 @@ public class ConfigurationTest { assertEquals("true", config4.getConfigValue("quarkus.log.handler.gelf.enabled").getValue()); } - @Test - public void testStorageMixedStorageOptions() { - System.setProperty(CLI_ARGS, "--storage=jpa" + ARG_SEPARATOR + "--storage-area-realm=chm"); - SmallRyeConfig config = createConfig(); - assertEquals("jpa", config.getConfigValue("kc.storage").getValue()); - assertNull(config.getConfigValue("kc.spi-map-storage-provider").getValue()); - assertEquals("map", config.getConfigValue("kc.spi-realm-provider").getValue()); - assertEquals("concurrenthashmap", config.getConfigValue("kc.spi-realm-map-storage-provider").getValue()); - assertEquals("map", config.getConfigValue("kc.spi-user-provider").getValue()); - assertEquals("jpa", config.getConfigValue("kc.spi-user-map-storage-provider").getValue()); - } - - @Test - public void testStoragePureJpa() { - System.setProperty(CLI_ARGS, "--storage=jpa"); - SmallRyeConfig config = createConfig(); - assertEquals("jpa", config.getConfigValue("kc.storage").getValue()); - assertNull(config.getConfigValue("kc.spi-map-storage-provider").getValue()); - assertEquals("map", config.getConfigValue("kc.spi-realm-provider").getValue()); - assertEquals("jpa", config.getConfigValue("kc.spi-realm-map-storage-provider").getValue()); - assertEquals("map", config.getConfigValue("kc.spi-user-provider").getValue()); - assertEquals("jpa", config.getConfigValue("kc.spi-user-map-storage-provider").getValue()); - } - @Test public void testOptionValueWithEqualSign() { System.setProperty(CLI_ARGS, "--db=postgres" + ARG_SEPARATOR + "--db-password=my_secret="); diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/IgnoredArtifactsTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/IgnoredArtifactsTest.java index da30cab9b6f..08b932279ca 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/IgnoredArtifactsTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/IgnoredArtifactsTest.java @@ -20,12 +20,9 @@ package org.keycloak.quarkus.runtime.configuration.test; import org.junit.Test; import org.keycloak.common.Profile; import org.keycloak.common.profile.PropertiesProfileConfigResolver; -import org.keycloak.config.StorageOptions; import org.keycloak.quarkus.runtime.configuration.IgnoredArtifacts; -import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import java.util.Properties; -import java.util.function.Consumer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -52,26 +49,4 @@ public class IgnoredArtifactsTest { var ignoredArtifacts = IgnoredArtifacts.getDefaultIgnoredArtifacts(); assertThat(ignoredArtifacts.containsAll(IgnoredArtifacts.FIPS_ENABLED), is(true)); } - - @Test - public void ignoredMapStorage() { - var ignoredArtifacts = IgnoredArtifacts.getDefaultIgnoredArtifacts(); - assertThat(ignoredArtifacts.containsAll(IgnoredArtifacts.MAP_STORE), is(true)); - - Consumer assertStorage = (storage) -> { - System.setProperty(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + StorageOptions.STORAGE.getKey(), storage); - - try { - final var artifacts = IgnoredArtifacts.getDefaultIgnoredArtifacts(); - assertThat(artifacts.containsAll(IgnoredArtifacts.MAP_STORE), is(false)); - } finally { - System.setProperty(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + StorageOptions.STORAGE.getKey(), ""); - } - }; - - assertStorage.accept("jpa"); - assertStorage.accept("hotrod"); - assertStorage.accept("file"); - assertStorage.accept("chm"); - } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java index 5682f21bc65..16ba48ded59 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java @@ -41,7 +41,7 @@ public class BuildAndStartDistTest { void testBuildAndStart(KeycloakDistribution dist) { RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); // start using based on the build options set via CLI - CLIResult cliResult = rawDist.run("build", "--storage=chm"); + CLIResult cliResult = rawDist.run("build", "--http-relative-path=/auth"); cliResult.assertBuild(); cliResult = rawDist.run("start", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG); cliResult.assertNoBuild(); @@ -50,7 +50,7 @@ public class BuildAndStartDistTest { // start using based on the build options set via conf file rawDist.setProperty("http-enabled", "true"); rawDist.setProperty("hostname-strict", "false"); - rawDist.setProperty("storage", "chm"); + rawDist.setProperty("http-relative-path", "/auth"); cliResult = rawDist.run("build"); cliResult.assertBuild(); cliResult = rawDist.run("start", OPTIMIZED_BUILD_OPTION_LONG); @@ -62,7 +62,7 @@ public class BuildAndStartDistTest { cliResult.assertStarted(); // remove the build option from conf file to force a build during start - rawDist.removeProperty("storage"); + rawDist.removeProperty("http-relative-path"); cliResult = rawDist.run("start"); cliResult.assertBuild(); cliResult.assertStarted(); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java index 7d24e24fdc7..74eae8bbd87 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java @@ -75,13 +75,13 @@ public class ImportAtStartupDistTest { @Test @BeforeStartDistribution(CreateRealmConfigurationFile.class) void testImportFromFileCreatedByExportAllRealms(KeycloakDistribution dist) throws IOException { - dist.run("start-dev", "--import-realm", "--storage=chm"); + dist.run("start-dev", "--import-realm"); dist.run("export", "--file=../data/import/realm.json"); RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("chm").toAbsolutePath()); + FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); - CLIResult result = dist.run("start-dev", "--import-realm", "--storage=chm"); + CLIResult result = dist.run("start-dev", "--import-realm"); result.assertMessage("Realm 'quickstart-realm' imported"); result.assertMessage("Realm 'master' already exists. Import skipped"); } @@ -89,13 +89,13 @@ public class ImportAtStartupDistTest { @Test @BeforeStartDistribution(CreateRealmConfigurationFile.class) void testImportFromFileCreatedByExportSingleRealm(KeycloakDistribution dist) throws IOException { - dist.run("start-dev", "--import-realm", "--storage=chm"); + dist.run("start-dev", "--import-realm"); dist.run("export", "--realm=quickstart-realm", "--file=../data/import/realm.json"); RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("chm").toAbsolutePath()); + FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); - CLIResult result = dist.run("start-dev", "--import-realm", "--storage=chm"); + CLIResult result = dist.run("start-dev", "--import-realm"); result.assertMessage("Realm 'quickstart-realm' imported"); result.assertNoMessage("Not importing realm master from file"); } @@ -103,14 +103,14 @@ public class ImportAtStartupDistTest { @Test @BeforeStartDistribution(CreateRealmConfigurationFile.class) void testImportFromDirCreatedByExport(KeycloakDistribution dist) throws IOException { - dist.run("start-dev", "--import-realm", "--storage=chm"); + dist.run("start-dev", "--import-realm"); RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("import").toAbsolutePath()); dist.run("export", "--dir=../data/import"); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("chm").toAbsolutePath()); + FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); - CLIResult result = dist.run("start-dev", "--import-realm", "--storage=chm"); + CLIResult result = dist.run("start-dev", "--import-realm"); result.assertMessage("Realm 'quickstart-realm' imported"); result.assertNoMessage("Not importing realm master from file"); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java deleted file mode 100644 index 0c87f650b13..00000000000 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.it.storage.map; - -import org.junit.Assert; -import org.junit.jupiter.api.Test; -import org.keycloak.it.junit5.extension.CLIResult; -import org.keycloak.it.junit5.extension.DistributionTest; -import org.keycloak.it.junit5.extension.RawDistOnly; -import org.keycloak.it.utils.RawDistRootPath; - -import io.quarkus.test.junit.main.Launch; -import io.quarkus.test.junit.main.LaunchResult; - -@RawDistOnly(reason = "Need to check dist path") -@DistributionTest -public class ChmStorageDistTest { - - @Test - @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--storage=chm" }) - void testStartUsingChmsStorage(LaunchResult result, RawDistRootPath distPath) { - CLIResult cliResult = (CLIResult) result; - assertExpectedMessages(cliResult, distPath); - cliResult.assertStarted(); - } - - @Test - @Launch({ "start-dev", "--storage=chm" }) - void testStartDevUsingChmsStorage(LaunchResult result, RawDistRootPath distPath) { - CLIResult cliResult = (CLIResult) result; - assertExpectedMessages(cliResult, distPath); - cliResult.assertStartedDevMode(); - } - - private void assertExpectedMessages(CLIResult cliResult, RawDistRootPath distPath) { - cliResult.assertMessage("Experimental features enabled: map-storage"); - cliResult.assertMessage("Hibernate ORM is disabled because no JPA entities were found"); - Assert.assertFalse(distPath.getDistRootPath().resolve("data").resolve("h2").toFile().exists()); - } -} diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java deleted file mode 100644 index 320ffe6fe4c..00000000000 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 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.it.storage.map; - -import io.quarkus.test.junit.main.Launch; -import io.quarkus.test.junit.main.LaunchResult; -import org.junit.jupiter.api.Test; -import org.keycloak.it.junit5.extension.CLIResult; -import org.keycloak.it.junit5.extension.DistributionTest; -import org.keycloak.it.junit5.extension.WithDatabase; - -@DistributionTest(removeBuildOptionsAfterBuild = true) -@WithDatabase(alias = "infinispan", buildOptions={"storage=hotrod"}) -public class HotRodStoreDistTest { - - @Test - @Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" }) - void testSuccessful(LaunchResult result) { - CLIResult cliResult = (CLIResult) result; - cliResult.assertMessage("Experimental features enabled: map-storage"); - cliResult.assertMessage("[org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory] (main) HotRod client configuration was successful."); - cliResult.assertStarted(); - } -} diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java deleted file mode 100644 index 013b8bae7ee..00000000000 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 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.it.storage.map; - -import org.junit.jupiter.api.Test; -import org.keycloak.it.junit5.extension.CLIResult; -import org.keycloak.it.junit5.extension.DistributionTest; -import org.keycloak.it.junit5.extension.WithDatabase; - -import io.quarkus.test.junit.main.Launch; -import io.quarkus.test.junit.main.LaunchResult; - -@DistributionTest(removeBuildOptionsAfterBuild = true) -@WithDatabase(alias = "postgres", buildOptions={"storage=jpa"}) -public class JPAStoreDistTest { - - @Test - @Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" }) - void testSuccessful(LaunchResult result) { - CLIResult cliResult = (CLIResult) result; - cliResult.assertMessage("Experimental features enabled: map-storage"); - cliResult.assertMessage("[org.keycloak.models.map.storage.jpa.liquibase.updater.MapJpaLiquibaseUpdaterProvider] (main) Initializing database schema. Using changelog META-INF"); - cliResult.assertStarted(); - } -} diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/MixedStoreDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/MixedStoreDistTest.java deleted file mode 100644 index 547d34820af..00000000000 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/MixedStoreDistTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.keycloak.it.storage.map; - -import io.quarkus.test.junit.main.Launch; -import io.quarkus.test.junit.main.LaunchResult; -import org.junit.jupiter.api.Test; -import org.keycloak.it.junit5.extension.CLIResult; -import org.keycloak.it.junit5.extension.DistributionTest; -import org.keycloak.it.junit5.extension.RawDistOnly; -import org.keycloak.it.junit5.extension.WithDatabase; -import org.keycloak.it.utils.RawDistRootPath; - -import java.io.File; -import java.nio.file.Paths; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -@DistributionTest -@RawDistOnly(reason = "Containers are immutable") -@WithDatabase(alias = "postgres", buildOptions = {"storage=jpa", "storage-area-realm=chm"}) -public class MixedStoreDistTest { - - @Test - @Launch({ "start", "--http-enabled=true", "--hostname-strict=false" }) - void testStartUsingMixedStorage(LaunchResult result, RawDistRootPath distRoot) { - CLIResult cliResult = (CLIResult) result; - cliResult.assertStarted(); - File chmRealmsFile = Paths.get(distRoot.getDistRootPath().toString(), "data","chm", "map-realms.json").toFile(); - assertTrue(chmRealmsFile.isFile(), "File for realms does not exist!"); - } -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml index fc0db114f98..7f5cba4c107 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml @@ -94,16 +94,6 @@ - - org.keycloak - keycloak-model-map - ${project.version} - - - org.keycloak - keycloak-model-map-hot-rod - ${project.version} - diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index e1c4395bf21..5db8d7d9d1b 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -17,7 +17,6 @@ package org.keycloak.testsuite.rest; -import org.infinispan.client.hotrod.RemoteCache; import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.http.HttpRequest; import org.keycloak.Config; @@ -46,11 +45,6 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; import org.keycloak.models.sessions.infinispan.changes.sessions.CrossDCLastSessionRefreshStoreFactory; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ResetTimeOffsetEvent; @@ -113,11 +107,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.file.Files; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collections; @@ -257,18 +248,6 @@ public class TestingResourceProvider implements RealmResourceProvider { public Map setTimeOffset(Map time) { int offset = Integer.parseInt(time.get("offset")); - // move time on Hot Rod server if present - // determine usage of Infinispan based on user sessions config - String userSessionProvider = Config.scope(UserSessionSpi.NAME, MapUserSessionProviderFactory.PROVIDER_ID, AbstractMapProviderFactory.CONFIG_STORAGE).get("provider"); - if (Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE) && "hotrod".equals(userSessionProvider)) { - RemoteCache scriptCache = session.getProvider(HotRodConnectionProvider.class).getRemoteCache(DefaultHotRodConnectionProviderFactory.SCRIPT_CACHE); - if (scriptCache != null) { - Map param = new HashMap<>(); - param.put("timeService", offset); - scriptCache.execute("InfinispanTimeServiceTask", param); - } - } - Time.setOffset(offset); // Time offset was restarted diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index dae7c005a66..652800f4bfb 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -125,11 +125,6 @@ com.google.guava guava - - org.keycloak - keycloak-model-map - ${project.version} - org.keycloak keycloak-model-legacy-services diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java index 9226139ba59..db834ff6980 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java @@ -52,9 +52,6 @@ import org.keycloak.admin.client.resource.ClientsResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.representations.AccessToken; @@ -94,12 +91,6 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest { public void beforeTest() { // userSessionProvider is used only to prevent tests from running in certain configs, should be removed once GHI #15410 is resolved. userSessionProvider = testingClient.server().fetch(session -> Config.getProvider(UserSessionSpi.NAME), String.class); - if (userSessionProvider.equals(MapUserSessionProviderFactory.PROVIDER_ID)) { - // append the storage provider in case of map - String mapStorageProvider = testingClient.server().fetch(session -> Config.scope(UserSessionSpi.NAME, - MapUserSessionProviderFactory.PROVIDER_ID, AbstractMapProviderFactory.CONFIG_STORAGE).get("provider"), String.class); - if (mapStorageProvider != null) userSessionProvider = userSessionProvider + "-" + mapStorageProvider; - } createClients(); } @@ -126,11 +117,6 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest { @Test public void concurrentLoginSingleUser() throws Throwable { - // remove this restriction once GHI #15410 is resolved. - Assume.assumeThat("Test does not work with ConcurrentHashMap storage", - userSessionProvider, - not(equalTo(MapUserSessionProviderFactory.PROVIDER_ID + "-" + ConcurrentHashMapStorageProviderFactory.PROVIDER_ID))); - log.info("*********************************************"); long start = System.currentTimeMillis(); @@ -195,11 +181,6 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest { @Test public void concurrentLoginMultipleUsers() throws Throwable { - // remove this restriction once GHI #15410 is resolved. - Assume.assumeThat("Test does not work with ConcurrentHashMap storage", - userSessionProvider, - not(equalTo(MapUserSessionProviderFactory.PROVIDER_ID + "-" + ConcurrentHashMapStorageProviderFactory.PROVIDER_ID))); - log.info("*********************************************"); long start = System.currentTimeMillis(); @@ -227,10 +208,6 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest { @Test public void concurrentCodeReuseShouldFail() throws Throwable { - Assume.assumeThat("Test does not work with ConcurrentHashMap storage", - userSessionProvider, - not(equalTo(MapUserSessionProviderFactory.PROVIDER_ID + "-" + ConcurrentHashMapStorageProviderFactory.PROVIDER_ID))); - log.info("*********************************************"); long start = System.currentTimeMillis(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index 4ce7f5a0a4c..0328a3d11bf 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -25,14 +25,6 @@ "provider": "${keycloak.eventsStore.provider:jpa}", "jpa": { "max-detail-length": "${keycloak.eventsStore.maxDetailLength:1000}" - }, - "map": { - "storage-admin-events": { - "provider": "${keycloak.adminEventsStore.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - }, - "storage-auth-events": { - "provider": "${keycloak.authEventsStore.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } } }, @@ -45,140 +37,58 @@ }, "deploymentState": { - "provider": "${keycloak.deploymentState.provider:jpa}", - "map": { - "resourcesVersionSeed": "1JZ379bzyOCFA" - } + "provider": "${keycloak.deploymentState.provider:jpa}" }, "globalLock": { - "provider": "${keycloak.globalLock.provider:dblock}", - "map": { - "storage": { - "provider": "${keycloak.lock.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.globalLock.provider:dblock}" }, "realm": { - "provider": "${keycloak.realm.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.realm.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.realm.provider:jpa}" }, "user": { - "provider": "${keycloak.user.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.user.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.user.provider:jpa}" }, "client": { - "provider": "${keycloak.client.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.client.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.client.provider:jpa}" }, "clientScope": { - "provider": "${keycloak.clientScope.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.clientScope.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.clientScope.provider:jpa}" }, "group": { - "provider": "${keycloak.group.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.group.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.group.provider:jpa}" }, "role": { - "provider": "${keycloak.role.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.role.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.role.provider:jpa}" }, "authenticationSessions": { "provider": "${keycloak.authSession.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.authSession.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - }, "infinispan": { "authSessionsLimit": "${keycloak.authSessions.limit:300}" } }, "userSessions": { - "provider": "${keycloak.userSession.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.userSession.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.userSession.provider:infinispan}" }, "loginFailure": { - "provider": "${keycloak.loginFailure.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.loginFailure.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.loginFailure.provider:infinispan}" }, "singleUseObject": { - "provider": "${keycloak.singleUseObject.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.singleUseObject.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.singleUseObject.provider:infinispan}" }, "publicKeyStorage": { - "provider": "${keycloak.publicKeyStorage.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.publicKeyStorage.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } - }, - - "mapStorage": { - "concurrenthashmap": { - "dir": "${project.build.directory:target}", - "keyType.single-use-objects": "string", - "keyType.realms": "string", - "keyType.authz-resource-servers": "string" - }, - "jpa": { - "url": "${keycloak.map.storage.connectionsJpa.url:}", - "user": "${keycloak.map.storage.connectionsJpa.user:}", - "password": "${keycloak.map.storage.connectionsJpa.password:}", - "driver": "org.postgresql.Driver", - "showSql": "${keycloak.map.storage.connectionsJpa,showSql:false}" - }, - "file": { - "dir": "${keycloak.map.storage.file.directory:target/file}" - } + "provider": "${keycloak.publicKeyStorage.provider:infinispan}" }, "userFederatedStorage": { @@ -190,12 +100,7 @@ }, "authorizationPersister": { - "provider": "${keycloak.authorization.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.authorization.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.authorization.provider:jpa}" }, "authorizationCache": { @@ -293,17 +198,6 @@ } }, - "connectionsHotRod": { - "default": { - "port": "${keycloak.connectionsHotRod.port:11222}", - "host": "${keycloak.connectionsHotRod.host:127.0.0.1}", - "configureRemoteCaches": "${keycloak.connectionsHotRod.configureRemoteCaches:true}", - "username": "${keycloak.connectionsHotRod.username:admin}", - "password": "${keycloak.connectionsHotRod.password:admin}", - "enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:true}" - } - }, - "truststore": { "file": { "file": "${keycloak.truststore.file:target/dependency/keystore/keycloak.truststore}", diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java index fce164a689a..39b8a2e390c 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java @@ -16,7 +16,6 @@ */ package org.keycloak.testsuite.model; -import org.infinispan.client.hotrod.RemoteCache; import org.junit.Assert; import org.keycloak.Config.Scope; import org.keycloak.authorization.AuthorizationSpi; @@ -46,8 +45,6 @@ import org.keycloak.models.DeploymentStateSpi; import org.keycloak.models.UserLoginFailureSpi; import org.keycloak.models.UserSessionSpi; import org.keycloak.models.UserSpi; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; import org.keycloak.models.locking.GlobalLockProviderSpi; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.PostMigrationEvent; @@ -66,7 +63,6 @@ import java.lang.management.LockInfo; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.util.Arrays; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -645,22 +641,11 @@ public abstract class KeycloakModelTest { } /** - * Moves time on the Keycloak server as well as on the remote Infinispan server if the Infinispan is used. - * @param seconds time offset in seconds by which Keycloak (and Infinispan) server time is moved + * Moves time on the Keycloak server + * @param seconds time offset in seconds by which Keycloak server time is moved */ protected void setTimeOffset(int seconds) { inComittedTransaction(session -> { - // move time on Hot Rod server if present - HotRodConnectionProvider hotRodConnectionProvider = session.getProvider(HotRodConnectionProvider.class); - if (hotRodConnectionProvider != null) { - RemoteCache scriptCache = hotRodConnectionProvider.getRemoteCache(DefaultHotRodConnectionProviderFactory.SCRIPT_CACHE); - if (scriptCache != null) { - Map param = new HashMap<>(); - param.put("timeService", seconds); - Object returnFromTask = scriptCache.execute("InfinispanTimeServiceTask", param); - LOG.info(returnFromTask); - } - } Time.setOffset(seconds); }); } diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json index 7dec62a73b7..dd6461bf71e 100755 --- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json +++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json @@ -14,174 +14,62 @@ "provider": "${keycloak.eventsStore.provider:jpa}", "jpa": { "max-detail-length": "${keycloak.eventsStore.maxDetailLength:1000}" - }, - "map": { - "storage-admin-events": { - "provider": "${keycloak.adminEventsStore.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - }, - "storage-auth-events": { - "provider": "${keycloak.authEventsStore.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } } }, "deploymentState": { - "provider": "${keycloak.deploymentState.provider:jpa}", - "map": { - "resourcesVersionSeed": "1JZ379bzyOCFA" - } + "provider": "${keycloak.deploymentState.provider:jpa}" }, "globalLock": { - "provider": "${keycloak.globalLock.provider:dblock}", - "map": { - "storage": { - "provider": "${keycloak.lock.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.globalLock.provider:dblock}" }, "realm": { - "provider": "${keycloak.realm.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.realm.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.realm.provider:jpa}" }, "client": { - "provider": "${keycloak.client.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.client.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.client.provider:jpa}" }, "clientScope": { - "provider": "${keycloak.clientScope.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.clientScope.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.clientScope.provider:jpa}" }, "group": { - "provider": "${keycloak.group.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.group.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.group.provider:jpa}" }, "role": { - "provider": "${keycloak.role.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.role.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.role.provider:jpa}" }, "authenticationSessions": { "provider": "${keycloak.authSession.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.authSession.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - }, "infinispan": { "authSessionsLimit": "${keycloak.authSessions.limit:300}" } }, "userSessions": { - "provider": "${keycloak.userSession.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.userSession.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.userSession.provider:infinispan}" }, "loginFailure": { - "provider": "${keycloak.loginFailure.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.loginFailure.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.loginFailure.provider:infinispan}" }, "singleUseObject": { - "provider": "${keycloak.singleUseObject.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.singleUseObject.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.singleUseObject.provider:infinispan}" }, "publicKeyStorage": { - "provider": "${keycloak.publicKeyStorage.provider:infinispan}", - "map": { - "storage": { - "provider": "${keycloak.publicKeyStorage.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } - }, - - "mapStorage": { - "provider": "${keycloak.mapStorage.provider:}", - "concurrenthashmap": { - "dir": "${project.build.directory:target/map}", - "keyType.single-use-objects": "string", - "keyType.realms": "string", - "keyType.authz-resource-servers": "string" - }, - "jpa": { - "url": "${keycloak.map.storage.connectionsJpa.url:}", - "user": "${keycloak.map.storage.connectionsJpa.user:}", - "password": "${keycloak.map.storage.connectionsJpa.password:}", - "driver": "org.postgresql.Driver", - "showSql": "${keycloak.map.storage.connectionsJpa,showSql:false}" - }, - "ldap-map-storage": { - "vendor": "other", - "usernameLDAPAttribute": "uid", - "rdnLDAPAttribute": "uid", - "uuidLDAPAttribute": "entryUUID", - "userObjectClasses": "inetOrgPerson, organizationalPerson", - "connectionUrl": "${keycloak.map.storage.ldap.connectionUrl:}", - "usersDn": "ou=People,dc=keycloak,dc=org", - "bindDn": "${keycloak.map.storage.ldap.bindDn:}", - "bindCredential": "${keycloak.map.storage.ldap.bindCredential:}", - "roles.realm.dn": "ou=RealmRoles,dc=keycloak,dc=org", - "roles.common.dn": "dc=keycloak,dc=org", - "roles.client.dn": "ou={0},dc=keycloak,dc=org", - "membership.ldap.attribute": "member", - "role.name.ldap.attribute": "cn", - "role.object.classes": "groupOfNames", - "role.attributes": "ou", - "mode": "LDAP_ONLY", - "use.realm.roles.mapping": "true", - "connectionPooling": "true" - }, - "file": { - "dir": "${keycloak.group.map.storage.provider.directory:target/file}" - } + "provider": "${keycloak.publicKeyStorage.provider:infinispan}" }, "user": { - "provider": "${keycloak.user.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.user.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.user.provider:jpa}" }, "userFederatedStorage": { @@ -193,12 +81,7 @@ }, "authorizationPersister": { - "provider": "${keycloak.authorization.provider:jpa}", - "map": { - "storage": { - "provider": "${keycloak.authorization.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" - } - } + "provider": "${keycloak.authorization.provider:jpa}" }, "theme": { @@ -267,19 +150,6 @@ } }, - "connectionsHotRod": { - "default": { - "embedded": "${keycloak.connectionsHotRod.embedded:true}", - "embeddedPort": "${keycloak.connectionsHotRod.embeddedPort:11444}", - "enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:false}", - "port": "${keycloak.connectionsHotRod.port:11444}", - "host": "${keycloak.connectionsHotRod.host:localhost}", - "configureRemoteCaches": "${keycloak.connectionsHotRod.configureRemoteCaches:false}", - "username": "${keycloak.connectionsHotRod.username:admin}", - "password": "${keycloak.connectionsHotRod.password:admin}", - "reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:}" - } - }, "scripting": { },