diff --git a/README.md b/README.md index 1c70ece8208d35f3dcc82a14c963211d7b1258e2..27d0110791315c6e801ddde881588825d5430e8f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,18 @@ This project uses Quarkus, the Supersonic Subatomic Java Framework. If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . +## Configuring the application +This properties mist be configured to run the application + +quarkus.mongodb.connection-string=<The connection string for the mongo db database> +quarkus.mongodb.database=<name of the mongo db database> +usermanager.keycloak.sync.cron=<default is "0 15 2 * * ?" > +usermanager.keycloak.api.user=< the name of the keycloak admin api user, default: goofyApiUser> +usermanager.keycloak.api.password=< the password of the keycloak admin api user> +usermanager.keycloak.api.realm=<The name of the realm> +usermanager.keycloak.api.organisations-einheit-id-key=< The key where the organisationsEinheitId of the group is saved, default is 'organisationseinheitId'> + + ## Running the application in dev mode You can run your application in dev mode that enables live coding using: @@ -65,3 +77,11 @@ Easily start your Reactive RESTful Web Services Monitor your application's health using SmallRye Health [Related guide section...](https://quarkus.io/guides/smallrye-health) + +## Best parctice + +### User package-private fields for injected fields + +This involves injection fields, constructors and initializers, observer methods, producer methods and fields, disposers and interceptor methods. + +[See section 2 of quarkus guide ](https://quarkus.io/guides/cdi-reference#native-executables-and-private-members) diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000000000000000000000000000000000000..4cbb865834f6128b8ddc16fc9b1966af99e52653 --- /dev/null +++ b/lombok.config @@ -0,0 +1,6 @@ +lombok.log.fieldName=LOG +lombok.log.slf4j.flagUsage = ERROR +lombok.log.log4j.flagUsage = ERROR +lombok.data.flagUsage = ERROR +lombok.nonNull.exceptionType = IllegalArgumentException +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3daf0219a83806d133fa5baba948b860e95527a9..2eedc3b34b128ce7a269b8ff82f5f49f22cba7a9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,10 +1,7 @@ <?xml version="1.0"?> -<project - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" - xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> - + <groupId>de.itvsh.kop.user</groupId> <artifactId>user-manager</artifactId> <version>0.2.0-SNAPSHOT</version> @@ -12,14 +9,14 @@ <properties> <skipITs>true</skipITs> - + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <compiler-plugin.version>3.8.1</compiler-plugin.version> <maven.compiler.release>17</maven.compiler.release> - - <!-- <kop-common.version>1.1.4-SNAPSHOT</kop-common.version> --> - + + <kop-common.version>1.1.4-SNAPSHOT</kop-common.version> + <jacoco.plugin.version>0.8.8</jacoco.plugin.version> <quarkus.platform.version>2.10.3.Final</quarkus.platform.version> <surefire-plugin.version>3.0.0-M7</surefire-plugin.version> @@ -27,10 +24,10 @@ <git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version> <quarkus-logging-json.version>1.1.1</quarkus-logging-json.version> - <!-- TODO Properties entfernen, sobald der Testcode draussen ist --> - <org.assertj-core.version>3.23.1</org.assertj-core.version> - <org.projectlombok.version>1.18.24</org.projectlombok.version> - <!-- --> + <!-- Versions already declared in kop-common-dependencies bom -> use bom as parent!? --> + <mapstruct.version>1.5.1.Final</mapstruct.version> + <lombok.version>1.18.24</lombok.version> + <commons-lang3.version>3.12.0</commons-lang3.version> </properties> <dependencyManagement> @@ -42,17 +39,17 @@ <type>pom</type> <scope>import</scope> </dependency> - <!-- <dependency> + <dependency> <groupId>de.itvsh.kop.common</groupId> <artifactId>kop-common-dependencies</artifactId> <version>${kop-common.version}</version> <type>pom</type> <scope>import</scope> - </dependency>--> + </dependency> </dependencies> </dependencyManagement> - <dependencies> + <dependencies> <!-- Quarkus --> <dependency> <groupId>io.quarkus</groupId> @@ -67,88 +64,124 @@ <artifactId>quarkus-config-yaml</artifactId> </dependency> <dependency> - <groupId>io.quarkiverse.loggingjson</groupId> - <artifactId>quarkus-logging-json</artifactId> - <version>${quarkus-logging-json.version}</version> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-micrometer-registry-prometheus</artifactId> </dependency> <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-micrometer-registry-prometheus</artifactId> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-smallrye-health</artifactId> </dependency> <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-smallrye-health</artifactId> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-keycloak-admin-client</artifactId> </dependency> - - <!-- Test --> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-junit5</artifactId> - <scope>test</scope> + <artifactId>quarkus-mongodb-client</artifactId> </dependency> <dependency> - <groupId>io.rest-assured</groupId> - <artifactId>rest-assured</artifactId> - <scope>test</scope> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-mongodb-panache</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-resteasy-reactive</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-scheduler</artifactId> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-collections4</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-oidc</artifactId> </dependency> + <!-- Logging --> + <dependency> + <groupId>io.quarkiverse.loggingjson</groupId> + <artifactId>quarkus-logging-json</artifactId> + <version>${quarkus-logging-json.version}</version> + </dependency> + <dependency> + <groupId>org.jboss.logmanager</groupId> + <artifactId>log4j2-jboss-logmanager</artifactId> + </dependency> + <!-- Tools --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-jacoco</artifactId> <scope>test</scope> </dependency> - <dependency> + <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-container-image-jib</artifactId> </dependency> - - <!-- TODO Dependency entfernen, sobald der Testcode draussen ist --> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <scope>provided</scope> + </dependency> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-spring-web</artifactId> + <artifactId>quarkus-test-keycloak-server</artifactId> + <scope>test</scope> </dependency> <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <version>${org.projectlombok.version}</version> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${mapstruct.version}</version> <scope>provided</scope> </dependency> - <!-- <dependency> - <groupId>org.testcontainers</groupId> - <artifactId>mongodb</artifactId> + <dependency> + <groupId>com.thedeanda</groupId> + <artifactId>lorem</artifactId> <scope>test</scope> </dependency> + + <!-- Test --> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-mongodb-panache</artifactId> - </dependency> --> + <artifactId>quarkus-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-junit5-mockito</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-resteasy-reactive</artifactId> - </dependency> + <artifactId>quarkus-panache-mock</artifactId> + <scope>test</scope> + </dependency> <dependency> - <groupId>org.testcontainers</groupId> - <artifactId>testcontainers</artifactId> + <groupId>io.rest-assured</groupId> + <artifactId>rest-assured</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> - <version>${org.assertj-core.version}</version> <scope>test</scope> - </dependency> - - <!-- - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-container-image-docker</artifactId> </dependency> <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-container-image-docker-deployment</artifactId> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> </dependency> - --> </dependencies> <build> @@ -172,10 +205,30 @@ <artifactId>maven-compiler-plugin</artifactId> <version>${compiler-plugin.version}</version> <configuration> + <fork>true</fork> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${lombok.version}</version> + </path> + <path> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${mapstruct.version}</version> + </path> + <!-- other annotation processors --> + </annotationProcessorPaths> <showWarnings>true</showWarnings> <compilerArgs> <arg>-parameters</arg> - </compilerArgs> + <compilerArg> + -Amapstruct.defaultComponentModel=cdi + </compilerArg> + <compilerArg> + -Amapstruct.unmappedTargetPolicy=WARN + </compilerArg> + </compilerArgs> </configuration> </plugin> <plugin> diff --git a/src/main/java/de/itvsh/kop/user/MyLivenessCheck.java b/src/main/java/de/itvsh/kop/user/LivenessCheck.java similarity index 84% rename from src/main/java/de/itvsh/kop/user/MyLivenessCheck.java rename to src/main/java/de/itvsh/kop/user/LivenessCheck.java index 96ba689ae99563a1209225ca2eec2d456f6ebf74..fd0e082d495fa4834483ec6540cae8ce04d21f55 100644 --- a/src/main/java/de/itvsh/kop/user/MyLivenessCheck.java +++ b/src/main/java/de/itvsh/kop/user/LivenessCheck.java @@ -5,7 +5,7 @@ import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Liveness; @Liveness -public class MyLivenessCheck implements HealthCheck { +public class LivenessCheck implements HealthCheck { @Override public HealthCheckResponse call() { diff --git a/src/main/java/de/itvsh/kop/user/RemoteUserIterator.java b/src/main/java/de/itvsh/kop/user/RemoteUserIterator.java new file mode 100644 index 0000000000000000000000000000000000000000..93958622763c86fb9c693f5316876d93434382e8 --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/RemoteUserIterator.java @@ -0,0 +1,47 @@ +package de.itvsh.kop.user; + +import java.util.Iterator; + +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.representations.idm.UserRepresentation; + +import com.cronutils.utils.StringUtils; + +public class RemoteUserIterator implements Iterator<UserRepresentation> { + + private static final int PAGE_SIZE = 10;// TODO + + private final UsersResource users; + private final int count; + + private int step = 0; + private Iterator<UserRepresentation> loadedUserIterator; + + public RemoteUserIterator(RealmResource resource) { + users = resource.users(); + count = users.count(); + loadNextStep(); + } + + @Override + public boolean hasNext() { + if (loadedUserIterator.hasNext()) { + return true; + } + if (count > step * PAGE_SIZE) { + loadNextStep(); + return loadedUserIterator.hasNext(); + } + return false; + } + + private void loadNextStep() { + loadedUserIterator = users.search(StringUtils.EMPTY, step * PAGE_SIZE, ++step * PAGE_SIZE).iterator(); + } + + @Override + public UserRepresentation next() { + return loadedUserIterator.next(); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/User.java b/src/main/java/de/itvsh/kop/user/User.java index 555588442b668e7d76741fd350ee10febb737a32..9b4f2dcb7babc42d9fe48757b29ec021fa3f63d0 100644 --- a/src/main/java/de/itvsh/kop/user/User.java +++ b/src/main/java/de/itvsh/kop/user/User.java @@ -1,17 +1,46 @@ package de.itvsh.kop.user; +import java.util.Collection; +import java.util.Date; + +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.types.ObjectId; + +import io.quarkus.mongodb.panache.common.MongoEntity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; -@Builder +@Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor @Getter +@Setter +@ToString public class User { - public String id; - public String name; - public String lastName; + public static final String EXTERNAL_ID_FIELD = "externalId"; + public static final String EMAIL_FIELD = "email"; + public static final String DELETED_FIELD = "deleted"; + public static final String LAST_SYNC_TIMESTAMP_FIELD = "lastSyncTimestamp"; + + @BsonId + private ObjectId id; + private String externalId; + private String firstName; + private String lastName; + private String username; + private String email; + private Collection<String> roles; + private Collection<String> organisationsEinheitIds; + private Date createdAt; + private Long lastSyncTimestamp; + private boolean deleted; + + public String getFullName() { + return String.format("%s %s", firstName, lastName); + } } \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/UserController.java b/src/main/java/de/itvsh/kop/user/UserController.java deleted file mode 100644 index 7489e73fe5adcaa73034ae1e21de72ad0e8de587..0000000000000000000000000000000000000000 --- a/src/main/java/de/itvsh/kop/user/UserController.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.itvsh.kop.user; - -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; - -@Path("/api/user") -public class UserController { - - @Inject - private UserService userService; - - @Path("/{userId}") - @GET - @Produces(MediaType.TEXT_PLAIN) - public ResponseEntity<String> getById(@PathVariable String userId) { - var user = userService.getById(userId); - - return ResponseEntity.ok(user.getName()); - } -} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/UserRepository.java b/src/main/java/de/itvsh/kop/user/UserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..06ea421478689d743906a680a75d0733b300851e --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/UserRepository.java @@ -0,0 +1,29 @@ +package de.itvsh.kop.user; + +import java.util.Optional; + +import javax.enterprise.context.ApplicationScoped; + +import org.apache.commons.lang3.StringUtils; + +import io.quarkus.mongodb.panache.PanacheMongoRepository; + +@ApplicationScoped +public class UserRepository implements PanacheMongoRepository<User> { + + public Optional<User> findByExternalId(String externalId) { + return find(User.EXTERNAL_ID_FIELD, externalId).firstResultOptional(); + } + + public Optional<User> findByEmail(String email) { + if (StringUtils.isNotEmpty(email)) { + return find(User.EMAIL_FIELD, email).firstResultOptional(); + } else { + return Optional.empty(); + } + } + + public long updateUnsyncedUsers(long lastSyncTimestamp) { + return update(User.DELETED_FIELD, true).where(User.LAST_SYNC_TIMESTAMP_FIELD + " < ?1", lastSyncTimestamp); + } +} diff --git a/src/main/java/de/itvsh/kop/user/UserResourceMapper.java b/src/main/java/de/itvsh/kop/user/UserResourceMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..fa1bc9b91404c371605623a97412eb18173667ca --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/UserResourceMapper.java @@ -0,0 +1,103 @@ +package de.itvsh.kop.user; + +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import javax.inject.Inject; + +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.ClientMappingsRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import de.itvsh.kop.user.keycloak.KeycloakApiProperties; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN) +public abstract class UserResourceMapper { + + @Inject + KeycloakApiProperties properties; + + @Inject + RealmResource realm; + + @Mapping(target = "createdAt", expression = "java(mapCreatedAt(userRes))") + @Mapping(target = "email", expression = "java(mapEmail(userRes))") + @Mapping(target = "firstName", expression = "java(mapFirstName(userRes))") + @Mapping(target = "lastName", expression = "java(mapLastName(userRes))") + @Mapping(target = "username", expression = "java(mapUsername(userRes))") + @Mapping(target = "externalId", expression = "java(mapId(userRes))") + @Mapping(target = "organisationsEinheitIds", expression = "java(mapOrganisationsEinheitIds(userRes))") + @Mapping(target = "roles", expression = "java(mapRoles(userRes))") + @Mapping(target = "lastSyncTimestamp", ignore = true) + @Mapping(target = "deleted", ignore = true) + public abstract User toKopUser(UserResource userRes); + + Date mapCreatedAt(UserResource userRes) { + var createdAt = userRes.toRepresentation().getCreatedTimestamp(); + + return createdAt != null ? new Date(createdAt) : new Date(); + } + + Set<String> mapOrganisationsEinheitIds(UserResource userRes) { + var groups = userRes.groups(); + var organisationsEinheitIds = getOrganisationsEinheitIdsFromGroups(groups); + return new HashSet<>(organisationsEinheitIds); + } + + private List<String> getOrganisationsEinheitIdsFromGroups(List<GroupRepresentation> groups) { + return groups.stream() + .map(group -> { + var groupFromRealm = realm.getGroupByPath(group.getPath()); + return groupFromRealm != null ? groupFromRealm.getAttributes() : null; + }) + .filter(Objects::nonNull) + .map(attributeMap -> attributeMap.get(properties.organisationsEinheitIdKey())) + .filter(Objects::nonNull) + .map(attributeValues -> attributeValues.get(0)) + .toList(); + } + + List<String> mapRoles(UserResource userRes) { + var roleRepresentation = Optional.ofNullable(userRes.roles().getAll().getClientMappings()) + .filter(Objects::nonNull) + .filter(map -> map.containsKey(properties.client())) + .map(map -> map.get(properties.client())) + .map(ClientMappingsRepresentation::getMappings) + .orElseGet(Collections::emptyList); + + return roleRepresentation.stream().map(RoleRepresentation::getName).toList(); + } + + String mapId(UserResource userRes) { + var userRepresentation = userRes.toRepresentation(); + var attributes = userRepresentation.getAttributes(); + var id = attributes != null ? attributes.get(properties.ldapIdKey()) : null; + return id != null ? id.get(0) : userRepresentation.getId(); + } + + String mapEmail(UserResource userRes) { + return userRes.toRepresentation().getEmail(); + } + + String mapFirstName(UserResource userRes) { + return userRes.toRepresentation().getFirstName(); + } + + String mapLastName(UserResource userRes) { + return userRes.toRepresentation().getLastName(); + } + + String mapUsername(UserResource userRes) { + return userRes.toRepresentation().getUsername(); + } +} diff --git a/src/main/java/de/itvsh/kop/user/UserService.java b/src/main/java/de/itvsh/kop/user/UserService.java index ff5b73b3be1c79c4c4ca203ff24205777c7011c3..b7c622d7f93bdb1314a20fa4e855d683b866a51a 100644 --- a/src/main/java/de/itvsh/kop/user/UserService.java +++ b/src/main/java/de/itvsh/kop/user/UserService.java @@ -1,21 +1,35 @@ package de.itvsh.kop.user; -import java.util.UUID; +import java.util.Optional; import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; @ApplicationScoped -class UserService { +public class UserService { - User getById(String id) { - return buildUser(id); + @Inject + UserRepository repository; + + public void save(User user) { + findUser(user).ifPresentOrElse(persistedUser -> repository.update(addIdUser(user, persistedUser)), + () -> repository.persist(user)); + } + + private Optional<User> findUser(User user) { + var dbUser = repository.findByExternalId(user.getExternalId()); + + if (dbUser.isEmpty()) { + dbUser = repository.findByEmail(user.getEmail()); + } + return dbUser; + } + + private User addIdUser(User user, User persistedUser) { + return user.toBuilder().id(persistedUser.getId()).build(); } - private User buildUser(String id) { - return User.builder() - .id(UUID.randomUUID().toString()) - .name("Max " + id) - .lastName("Mustermann" + id) - .build(); + public void markUnsyncedUsersAsDeleted(long lastSyncTimestamp) { + repository.updateUnsyncedUsers(lastSyncTimestamp); } } \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/common/errorhandling/KeycloakClientException.java b/src/main/java/de/itvsh/kop/user/common/errorhandling/KeycloakClientException.java new file mode 100644 index 0000000000000000000000000000000000000000..3f4a77c69b720246c5e3aa0f24377d4558f4c3fe --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/common/errorhandling/KeycloakClientException.java @@ -0,0 +1,12 @@ +package de.itvsh.kop.user.common.errorhandling; + +public class KeycloakClientException extends TechnicalException { + + private static final long serialVersionUID = 1L; + + private static final String MESSAGE_TEMPLATE = "Access Denied for admin user: %s, realm: %s, url: %s"; + + public KeycloakClientException(String user, String realm, String url, Throwable cause) { + super(String.format(MESSAGE_TEMPLATE, user, realm, url), cause); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/common/errorhandling/KeycloakUnavailableException.java b/src/main/java/de/itvsh/kop/user/common/errorhandling/KeycloakUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..f1dc84229ab5ee2c3975f8653335cffe6297c104 --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/common/errorhandling/KeycloakUnavailableException.java @@ -0,0 +1,12 @@ +package de.itvsh.kop.user.common.errorhandling; + +public class KeycloakUnavailableException extends TechnicalException { + + private static final long serialVersionUID = 1L; + + private static final String MESSAGE_TEMPLATE = "Error performing on keycloak using admin user: %s, realm: %s, url: %s"; + + public KeycloakUnavailableException(String user, String realm, String url, Throwable cause) { + super(String.format(MESSAGE_TEMPLATE, user, realm, url, cause), cause); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/common/errorhandling/LockCreationException.java b/src/main/java/de/itvsh/kop/user/common/errorhandling/LockCreationException.java new file mode 100644 index 0000000000000000000000000000000000000000..5ee7a37e2ca833c9a53afd9e9d9f8cc64cd8abed --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/common/errorhandling/LockCreationException.java @@ -0,0 +1,12 @@ +package de.itvsh.kop.user.common.errorhandling; + +public class LockCreationException extends TechnicalException { + + private static final long serialVersionUID = 1L; + + private static final String MESSAGE = "Unable to find created lock."; + + public LockCreationException() { + super(MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/common/errorhandling/TechnicalException.java b/src/main/java/de/itvsh/kop/user/common/errorhandling/TechnicalException.java new file mode 100644 index 0000000000000000000000000000000000000000..aec1d123d7ab6487a034bbfe055fe97f4c5d525c --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/common/errorhandling/TechnicalException.java @@ -0,0 +1,27 @@ +package de.itvsh.kop.user.common.errorhandling; + +import java.util.UUID; + +public class TechnicalException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String exceptionId = UUID.randomUUID().toString(); + + public TechnicalException(String msg) { + super(msg); + } + + public TechnicalException(String msg, Throwable cause) { + super(msg, cause); + } + + @Override + public String getMessage() { + return formatMessage(); + } + + private String formatMessage() { + return String.format("%s (ExceptionId: %s)", super.getMessage(), exceptionId); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/common/errorhandling/UserNotFoundException.java b/src/main/java/de/itvsh/kop/user/common/errorhandling/UserNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..d0b53d0f59e1497f273539139650489f2e8130cb --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/common/errorhandling/UserNotFoundException.java @@ -0,0 +1,12 @@ +package de.itvsh.kop.user.common.errorhandling; + +public class UserNotFoundException extends TechnicalException { + + private static final long serialVersionUID = 1L; + + private static final String MESSAGE_WITH_EXTERNALID_TEMPLATE = "User not found for keycloak id %s"; + + public UserNotFoundException(String externalId, Throwable cause) { + super(String.format(MESSAGE_WITH_EXTERNALID_TEMPLATE, externalId), cause); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiProperties.java b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..b6b1776ebbb3c8e49bde40dde1beece6fdbda13a --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiProperties.java @@ -0,0 +1,23 @@ +package de.itvsh.kop.user.keycloak; + +import javax.validation.constraints.NotBlank; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "usermanager.keycloak.api") +public interface KeycloakApiProperties { + + @NotBlank + String user(); + + @NotBlank + String password(); + + String realm(); + + String client(); + + String organisationsEinheitIdKey(); + + String ldapIdKey(); +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java new file mode 100644 index 0000000000000000000000000000000000000000..e98bc4b43da8940e5a12c6bee1324e18642cb4f6 --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java @@ -0,0 +1,67 @@ +package de.itvsh.kop.user.keycloak; + +import java.util.Spliterators; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.ProcessingException; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.UserRepresentation; + +import de.itvsh.kop.user.RemoteUserIterator; +import de.itvsh.kop.user.User; +import de.itvsh.kop.user.UserResourceMapper; +import de.itvsh.kop.user.common.errorhandling.KeycloakClientException; +import de.itvsh.kop.user.common.errorhandling.KeycloakUnavailableException; +import io.quarkus.logging.Log; + +@ApplicationScoped +class KeycloakApiService { + + @Inject + RealmResource realmResource; + @Inject + UserResourceMapper mapper; + @Inject + KeycloakApiProperties properties; + + @ConfigProperty(name = "keycloak.url") + String keycloakUrl; + + public Stream<User> findAllUser() { + return handlingKeycloakException(() -> getAllUserRepresentation().map(this::toUser)); + } + + Stream<UserRepresentation> getAllUserRepresentation() { + var iterator = createRealmUserIterator(); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); + } + + private RemoteUserIterator createRealmUserIterator() { + return new RemoteUserIterator(realmResource); + } + + private User toUser(UserRepresentation userRepresentation) { + return mapper.toKopUser(realmResource.users().get(userRepresentation.getId())); + } + + private <T> T handlingKeycloakException(Supplier<T> runnable) { + try { + return runnable.get(); + } catch (ClientErrorException e) { + var exception = new KeycloakClientException(properties.user(), properties.realm(), keycloakUrl, e); + Log.error(exception.getMessage()); + throw exception; + } catch (ProcessingException | IllegalStateException e) { + var exception = new KeycloakUnavailableException(properties.user(), properties.realm(), keycloakUrl, e); + Log.error(exception.getMessage()); + throw exception; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/keycloak/KeycloakProvider.java b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..70e1355f6a6e6f7c031d481a61460fe5ee9931d3 --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakProvider.java @@ -0,0 +1,35 @@ +package de.itvsh.kop.user.keycloak; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.RealmResource; + +public class KeycloakProvider { + + private static final String CLIENT = "admin-cli"; + + @Inject + KeycloakApiProperties keycloakConfiguration; + + @ConfigProperty(name = "keycloak.url") + String keycloakUrl; + + @ApplicationScoped + RealmResource provide() { + return buildKeycloakInstance().realm(keycloakConfiguration.realm()); + } + + private Keycloak buildKeycloakInstance() { + return KeycloakBuilder.builder() + .serverUrl(keycloakUrl) + .realm(keycloakConfiguration.realm()) + .clientId(CLIENT) + .username(keycloakConfiguration.user()) + .password(keycloakConfiguration.password()) + .build(); + } +} diff --git a/src/main/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteService.java b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteService.java new file mode 100644 index 0000000000000000000000000000000000000000..56c33762474e079e265d503c28cccafbcbee554a --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteService.java @@ -0,0 +1,19 @@ +package de.itvsh.kop.user.keycloak; + +import java.util.stream.Stream; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import de.itvsh.kop.user.User; + +@ApplicationScoped +public class KeycloakUserRemoteService { + + @Inject + KeycloakApiService apiService; + + public Stream<User> getAllUsers() { + return apiService.findAllUser(); + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/sync/Lock.java b/src/main/java/de/itvsh/kop/user/sync/Lock.java new file mode 100644 index 0000000000000000000000000000000000000000..a604b1e597b6330aa536d5891c031a8714ba104b --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/sync/Lock.java @@ -0,0 +1,22 @@ +package de.itvsh.kop.user.sync; + +import io.quarkus.mongodb.panache.PanacheMongoEntity; +import io.quarkus.mongodb.panache.common.MongoEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@MongoEntity +public class Lock extends PanacheMongoEntity { + + public static final String TIMESTAMP_FIELD = "timestamp"; + + private long timestamp; +} diff --git a/src/main/java/de/itvsh/kop/user/sync/LockRepository.java b/src/main/java/de/itvsh/kop/user/sync/LockRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..fd13f5d1696b681ff0ec1530831723c09895bfaa --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/sync/LockRepository.java @@ -0,0 +1,20 @@ +package de.itvsh.kop.user.sync; + +import java.time.Instant; +import java.util.Optional; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.mongodb.panache.PanacheMongoRepository; + +@ApplicationScoped +class LockRepository implements PanacheMongoRepository<Lock> { + + Optional<Lock> findLockBefore(Instant timestamp) { + return find(Lock.TIMESTAMP_FIELD + " < ?1", timestamp.toEpochMilli()).firstResultOptional(); + } + + Optional<Lock> findLock(Instant timestamp) { + return find(Lock.TIMESTAMP_FIELD, timestamp.toEpochMilli()).firstResultOptional(); + } +} diff --git a/src/main/java/de/itvsh/kop/user/sync/LockService.java b/src/main/java/de/itvsh/kop/user/sync/LockService.java new file mode 100644 index 0000000000000000000000000000000000000000..57710a665a312e5e8df854d67f0e67d8fe7497ed --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/sync/LockService.java @@ -0,0 +1,47 @@ +package de.itvsh.kop.user.sync; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import de.itvsh.kop.user.common.errorhandling.LockCreationException; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@ApplicationScoped +public class LockService { + + @Inject + LockRepository repository; + + public boolean isNotLocked() { + return repository.count() == 0; + } + + boolean isLockOlderThanADay() { + return repository.findLockBefore(Instant.now()) + .map(this::isSyncLockOlderThanADay) + .orElse(false); + } + + private boolean isSyncLockOlderThanADay(Lock syncLock) { + var lastSyncTimeStamp = Instant.ofEpochMilli(syncLock.getTimestamp()); + return lastSyncTimeStamp.plus(1, ChronoUnit.DAYS).isBefore(Instant.now()); + } + + public void unlock(Lock lock) { + repository.delete(lock); + LOG.info("Lock deleted"); + } + + public void lock(Instant timestamp) { + repository.persist(Lock.builder().timestamp(timestamp.toEpochMilli()).build()); + LOG.info("Lock created"); + } + + public Lock getByTimestamp(Instant timestamp) { + return repository.findLock(timestamp).orElseThrow(LockCreationException::new); + } +} diff --git a/src/main/java/de/itvsh/kop/user/sync/SyncScheduler.java b/src/main/java/de/itvsh/kop/user/sync/SyncScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..d72afb46754af0bcd912682e2f09a4a6478bb764 --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/sync/SyncScheduler.java @@ -0,0 +1,29 @@ +package de.itvsh.kop.user.sync; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.quarkus.scheduler.Scheduled; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@ApplicationScoped +public class SyncScheduler { + + @Inject + SyncService syncService; + @Inject + LockService syncLockService; + + @Scheduled(cron = "{usermanager.keycloak.sync.cron}") + public void start() { + if (syncLockService.isNotLocked()) { + syncService.sync(); + } else { + LOG.warn("UserManager: Database is locked, no sync happend."); + if (syncLockService.isLockOlderThanADay()) { + LOG.warn("Database lock is older than a day"); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/de/itvsh/kop/user/sync/SyncService.java b/src/main/java/de/itvsh/kop/user/sync/SyncService.java new file mode 100644 index 0000000000000000000000000000000000000000..2a597fb129821b3385f44dd8aa006bf036eba373 --- /dev/null +++ b/src/main/java/de/itvsh/kop/user/sync/SyncService.java @@ -0,0 +1,55 @@ +package de.itvsh.kop.user.sync; + +import java.time.Instant; +import java.util.function.Predicate; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.apache.commons.collections4.CollectionUtils; + +import de.itvsh.kop.user.User; +import de.itvsh.kop.user.UserService; +import de.itvsh.kop.user.keycloak.KeycloakUserRemoteService; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@ApplicationScoped +class SyncService { + + private final Predicate<User> HAS_ANY_ROLE = user -> CollectionUtils.isNotEmpty(user.getRoles());// NOSONAR + + @Inject + UserService userService; + @Inject + KeycloakUserRemoteService keycloakService; + @Inject + LockService lockService; + + public void sync() { + var now = Instant.now(); + lockService.lock(now); + + doSync(lockService.getByTimestamp(now)); + } + + void doSync(Lock lock) { + LOG.info("Start sync..."); + try { + keycloakService.getAllUsers() + .filter(HAS_ANY_ROLE) + .forEach(user -> userService.save(addLastSyncTimestamp(user, lock))); + + userService.markUnsyncedUsersAsDeleted(lock.getTimestamp()); + LOG.info("sync successful."); + } catch (Exception e) { + LOG.error("Error syncing user", e); + } finally { + lockService.unlock(lock); + } + } + + private User addLastSyncTimestamp(User user, Lock lock) { + return user.toBuilder().lastSyncTimestamp(lock.getTimestamp()).build(); + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ef51b4d92c3dfbae315ed3f489e8060dec02034b --- /dev/null +++ b/src/main/resources/application-dev.yaml @@ -0,0 +1,17 @@ +quarkus: + mongodb: + database: dev-user-manager + keycloak: + devservices: + create-realm: true + realm-name: dev + realm-path: dev-realm.json +usermanager: + keycloak: + sync: + cron: "0 */10 * * * ?" + api: + user: goofyApiUser + password: S9UEMuLG9y9ev99 + realm: dev + organisations-einheit-id-key: organisationsEinheitId diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d3116f7d59443a751cf1710f42947e3eebe952fb --- /dev/null +++ b/src/main/resources/application-local.yaml @@ -0,0 +1,6 @@ +quarkus: + port: 8082 + mongodb: + connection-string: mongodb://localhost:27017 +keycloak: + url: http://localhost:8088 \ No newline at end of file diff --git a/src/main/resources/application-remotekc.yaml b/src/main/resources/application-remotekc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f16c633e13820f042de61db668301921d9e4ffb1 --- /dev/null +++ b/src/main/resources/application-remotekc.yaml @@ -0,0 +1,11 @@ +usermanager: + keycloak: + sync: + cron: "* */10 * * * ?" + api: + user: goofyApiUser + password: S9UEMuLG9y9ev99 + realm: sh-kiel-dev + organisations-einheit-id-key: organisationseinheitId +keycloak: + url: https://sso.dev.ozg-sh.de/auth \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f86dccd2cc523c277713fd8c6b37b2ab37ecb136..14a06cba56e00d0d5462330822b03c752638225d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,10 +1,26 @@ quarkus: application: - name: KopUserManager + name: kopusermanager + mongodb: + database: usermanager + scheduler: + metrics: + enabled: true log: level: INFO console: - json: - fields: - level: - field-name: log.level \ No newline at end of file + json: + fields: + level: + field-name: log.level +usermanager: + keycloak: + sync: + cron: "0 15 2 * * ?" + api: + user: goofyApiUser + password: S9UEMuLG9y9ev99 + realm: sh-kiel-dev + organisations-einheit-id-key: organisationseinheitId + ldap-id-key: LDAP_ID + client: sh-kiel-dev-goofy \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/GroupRepresentationTestFactory.java b/src/test/java/de/itvsh/kop/user/GroupRepresentationTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7d1dfadb544dabc643658d869ec769b27ae0d695 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/GroupRepresentationTestFactory.java @@ -0,0 +1,23 @@ +package de.itvsh.kop.user; + +import java.util.List; +import java.util.Map; + +import org.keycloak.representations.idm.GroupRepresentation; + +class GroupRepresentationTestFactory { + static final GroupRepresentation createGroup(String path) { + var group = new GroupRepresentation(); + group.setPath(path); + return group; + } + + public static GroupRepresentation createByPathAndOrganisationEinheitId(String groupPath, String organisationEinheitId) { + var groupRepresentation = new GroupRepresentation(); + groupRepresentation.setName(groupPath); + groupRepresentation.setPath(groupPath); + groupRepresentation.setAttributes(Map.of(UserResourceMapperTest.ORGANISATIONS_EINHEIT_ID_KEY, + List.of(organisationEinheitId))); + return groupRepresentation; + } +} diff --git a/src/test/java/de/itvsh/kop/user/UserControllerTest.java b/src/test/java/de/itvsh/kop/user/UserControllerTest.java deleted file mode 100644 index 9fc989bbbc219228c07def6fca597f2129046c3d..0000000000000000000000000000000000000000 --- a/src/test/java/de/itvsh/kop/user/UserControllerTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.itvsh.kop.user; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -class UserControllerTest { - - @Test - void getById() { - given() - .when().get("/api/user/1") - .then() - .statusCode(200).body(is("Max 1")); - } -} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/UserITCase.java b/src/test/java/de/itvsh/kop/user/UserITCase.java deleted file mode 100644 index 673e9233e951ac9d90569750406773d4a7317bf0..0000000000000000000000000000000000000000 --- a/src/test/java/de/itvsh/kop/user/UserITCase.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.itvsh.kop.user; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -@QuarkusIntegrationTest -class UserITCase extends UserControllerTest { -} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java b/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..442db85e3ecdb9e2d770e01a8b1a94db41abe0c2 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java @@ -0,0 +1,56 @@ +package de.itvsh.kop.user; + +import javax.inject.Inject; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class UserRepositoryTest { + + @Inject + UserRepository repository; + + private User user1; + private User user2; + + @BeforeEach + void init() { + user1 = UserTestFactory.create(); + repository.persist(user1); + + user2 = UserTestFactory.createBuilder().externalId("unknown").build(); + repository.persist(user2); + user2 = user2.toBuilder().externalId(null).build(); + } + + @Test + void shouldFindByExternalId() { + var res = repository.findByExternalId(user1.getExternalId()); + + assertThat(res).isPresent(); + assertThat(res.get().getExternalId()).isEqualTo(user1.getExternalId()); + } + + @Test + void shouldFindByEmailOnly() { + var res = repository.findByEmail(user1.getEmail()); + + assertThat(res).isPresent(); + assertThat(res.get().getExternalId()).isEqualTo(user1.getExternalId()); + } + + @Test + void shouldNotFindByEmptyEmail() { + var user = UserTestFactory.createBuilder().externalId("unknown").email(null).build(); + repository.persist(user); + user = user.toBuilder().externalId(null).build(); + var res = repository.findByEmail(user.getEmail()); + + assertThat(res).isEmpty(); + } +} diff --git a/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java b/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f1c8a5504cf50231d55f7a78c8afbfc138f76bb9 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java @@ -0,0 +1,52 @@ +package de.itvsh.kop.user; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import org.keycloak.representations.idm.UserRepresentation; + +import com.thedeanda.lorem.LoremIpsum; + +public class UserRepresentationTestFactory { + + static final String EMAIL = LoremIpsum.getInstance().getEmail(); + static final String FIRST_NAME = LoremIpsum.getInstance().getFirstName(); + static final String LAST_NAME = LoremIpsum.getInstance().getLastName(); + static final String USER_NAME = LoremIpsum.getInstance().getName(); + + static final String EXTERNAL_ID = "external-id-1-ldap"; + static final String EXTERNAL_ID_FALLBACK = "external-id-2-keykloak"; + static final String ROLE_NAME = "VERWALTUNG_USER"; + + private static final long CREATED = Instant.now().toEpochMilli(); + + static final String ORGANSISATIONS_EINHEIT_ID = "0815"; + + private static final String LDAP_ID_KEY = "LDAP_ID"; + static final Map<String, List<String>> ATTRIBUTES = Map.of(LDAP_ID_KEY, List.of(EXTERNAL_ID)); + + static final String CLIENT_KEY = "sh-kiel-dev-goofy"; + private static final Map<String, List<String>> CLIENT_ROLED = Map.of(CLIENT_KEY, List.of(ROLE_NAME)); + + static UserRepresentation createWithAttributes(Map<String, List<String>> attributes) { + var user = create(); + user.setAttributes(attributes); + + return user; + } + + public static UserRepresentation create() { + var user = new UserRepresentation(); + user.setEmail(EMAIL); + user.setFirstName(FIRST_NAME); + user.setLastName(LAST_NAME); + user.setUsername(USER_NAME); + user.setCreatedTimestamp(CREATED); + user.setId(EXTERNAL_ID_FALLBACK); + user.setAttributes(ATTRIBUTES); + user.setClientRoles(CLIENT_ROLED); + + return user; + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/UserResourceMapperTest.java b/src/test/java/de/itvsh/kop/user/UserResourceMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7aadfde8b07262e820ef527fb09ccc24ac4b8887 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserResourceMapperTest.java @@ -0,0 +1,227 @@ +package de.itvsh.kop.user; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.RoleMappingResource; +import org.keycloak.admin.client.resource.RoleScopeResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.ClientMappingsRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.MappingsRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import de.itvsh.kop.user.keycloak.KeycloakApiProperties; + +class UserResourceMapperTest { + static final String ORGANISATIONS_EINHEIT_ID_KEY = "organisationseinheitId"; + static final String ORGANISATIONS_EINHEIT_ID_1 = "0815"; + static final String ORGANISATIONS_EINHEIT_ID_2 = "4711"; + static final String GROUP_1_PATH = "/group1"; + static final String GROUP_2_PATH = "/group2"; + + static final Map<String, List<String>> ATTRIBUTES_1 = Map.of(ORGANISATIONS_EINHEIT_ID_KEY, List.of(ORGANISATIONS_EINHEIT_ID_1)); + static final Map<String, List<String>> ATTRIBUTES_2 = Map.of(ORGANISATIONS_EINHEIT_ID_KEY, List.of(ORGANISATIONS_EINHEIT_ID_2)); + + @InjectMocks + private UserResourceMapper mapper = Mappers.getMapper(UserResourceMapper.class); + + @Mock + private KeycloakApiProperties properties; + + @Mock + private RealmResource realm; + + @Nested + class TestMapping { + + @BeforeEach + void init() { + when(properties.ldapIdKey()).thenReturn("LDAP_ID"); + when(properties.organisationsEinheitIdKey()).thenReturn("organisationseinheitId"); + when(properties.client()).thenReturn("sh-kiel-dev-goofy"); + when(realm.getGroupByPath(GROUP_1_PATH)) + .thenReturn(GroupRepresentationTestFactory.createByPathAndOrganisationEinheitId(GROUP_1_PATH, ORGANISATIONS_EINHEIT_ID_1)); + when(properties.ldapIdKey()).thenReturn("LDAP_ID"); + when(properties.organisationsEinheitIdKey()).thenReturn("organisationseinheitId"); + when(properties.client()).thenReturn("sh-kiel-dev-goofy"); + } + + @Test + void shouldMapToUser() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user).isNotNull(); + } + + @Test + void shouldMapEmail() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getEmail()).isEqualTo(UserRepresentationTestFactory.EMAIL); + } + + @Test + void shouldMapExternalId() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getExternalId()).isEqualTo(UserRepresentationTestFactory.EXTERNAL_ID); + } + + @Test + void shouldMapExternalIdFallback() { + User user = mapper.toKopUser(UserResourceTestFactory.createWithAttributes(Map.of())); + + assertThat(user.getExternalId()).isEqualTo(UserRepresentationTestFactory.EXTERNAL_ID_FALLBACK); + } + + @Test + void shouldMapFirstName() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getFirstName()).isEqualTo(UserRepresentationTestFactory.FIRST_NAME); + } + + @Test + void shouldMapLastName() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getLastName()).isEqualTo(UserRepresentationTestFactory.LAST_NAME); + } + + @Test + void shouldMapUserName() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getUsername()).isEqualTo(UserRepresentationTestFactory.USER_NAME); + } + + @Test + void shouldMapOrganisationsEinheitIds() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getOrganisationsEinheitIds()).isNotEmpty().contains(ORGANISATIONS_EINHEIT_ID_1); + } + + @Test + void shouldMapMultipleOrganisationsEinheitIds() { + GroupRepresentation groupRepresentation = GroupRepresentationTestFactory.createByPathAndOrganisationEinheitId(GROUP_2_PATH, + ORGANISATIONS_EINHEIT_ID_2); + + when(realm.getGroupByPath(GROUP_2_PATH)).thenReturn(groupRepresentation); + + User user = mapper.toKopUser( + UserResourceTestFactory.createWithGroups(List.of(GroupRepresentationTestFactory.createGroup(UserResourceMapperTest.GROUP_1_PATH), + GroupRepresentationTestFactory.createGroup(UserResourceMapperTest.GROUP_2_PATH)))); + + assertThat(user.getOrganisationsEinheitIds()).isNotEmpty().hasSize(2).contains(ORGANISATIONS_EINHEIT_ID_2); + } + + @Test + void shouldMapRoles() { + User user = mapper.toKopUser(UserResourceTestFactory.create()); + + assertThat(user.getRoles()).isNotEmpty().contains(UserRepresentationTestFactory.ROLE_NAME); + } + } + + @DisplayName("Get client roles") + @Nested + class TestGetClientRoles { + + @Mock + private UserResource userResource; + + @Mock + private RoleMappingResource roleMappingResource; + @Mock + private RoleScopeResource roleScopeResource; + @Mock + private MappingsRepresentation mappingsRepresentation; + @Mock + private Map<String, ClientMappingsRepresentation> clientMappingsRepresentation; + @Mock + private ClientMappingsRepresentation clientMappingRepresentation; + + @BeforeEach + void init() { + when(userResource.roles()).thenReturn(roleMappingResource); + when(roleMappingResource.getAll()).thenReturn(mappingsRepresentation); + } + + @DisplayName("on existing roles") + @Nested + class TestOnAssignedRoles { + + @BeforeEach + void init() { + when(properties.client()).thenReturn(UserRepresentationTestFactory.CLIENT_KEY); + + when(mappingsRepresentation.getClientMappings()).thenReturn(clientMappingsRepresentation); + when(clientMappingsRepresentation.containsKey(UserRepresentationTestFactory.CLIENT_KEY)).thenReturn(true); + when(clientMappingsRepresentation.get(UserRepresentationTestFactory.CLIENT_KEY)).thenReturn(clientMappingRepresentation); + when(clientMappingRepresentation.getMappings()).thenReturn(List.of(createRoleRepresentation())); + } + + private RoleRepresentation createRoleRepresentation() { + var roleRepresentation = new RoleRepresentation(); + roleRepresentation.setName(UserRepresentationTestFactory.ROLE_NAME); + return roleRepresentation; + } + + @Test + void shouldReturnRolesIfExists() { + var roles = mapper.mapRoles(userResource); + + assertThat(roles).isNotEmpty(); + assertThat(roles.get(0)).isEqualTo(UserRepresentationTestFactory.ROLE_NAME); + } + } + + @Nested + class TestOnNonExistingClient { + + @BeforeEach + void init() { + when(properties.client()).thenReturn(UserRepresentationTestFactory.CLIENT_KEY); + + when(mappingsRepresentation.getClientMappings()).thenReturn(Collections.emptyMap()); + } + + @Test + void shouldReturnEmptyListIfNoRolesAttached() { + var roles = mapper.mapRoles(userResource); + + assertThat(roles).isEmpty(); + } + } + + @Nested + class TestNullClientMappings { + + @BeforeEach + void init() { + when(mappingsRepresentation.getClientMappings()).thenReturn(null); + } + + @Test + void shouldReturnEmptyListIfNoRolesAttached() { + var roles = mapper.mapRoles(userResource); + + assertThat(roles).isEmpty(); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/UserResourceStub.java b/src/test/java/de/itvsh/kop/user/UserResourceStub.java new file mode 100644 index 0000000000000000000000000000000000000000..9ed3beaf6df8c4bdc0145dfecf0b96afc11c9888 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserResourceStub.java @@ -0,0 +1,241 @@ +package de.itvsh.kop.user; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.keycloak.admin.client.resource.RoleMappingResource; +import org.keycloak.admin.client.resource.RoleScopeResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.ClientMappingsRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.FederatedIdentityRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.MappingsRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +class UserResourceStub implements UserResource { + private UserRepresentation userRepresentation = UserRepresentationTestFactory.create(); + private List<GroupRepresentation> groups = List.of(GroupRepresentationTestFactory.createGroup(UserResourceMapperTest.GROUP_1_PATH)); + + public UserResourceStub(Map<String, List<String>> attributes) { + userRepresentation = UserRepresentationTestFactory.createWithAttributes(attributes); + } + + public UserResourceStub(List<GroupRepresentation> groups) { + this.groups = groups; + } + + @Override + public UserRepresentation toRepresentation() { + return userRepresentation; + } + + @Override + public void update(UserRepresentation userRepresentation) { + // not implemented + } + + @Override + public void remove() { + // not implemented + } + + @Override + public List<GroupRepresentation> groups() { + return groups; + } + + @Override + public void leaveGroup(String groupId) { + // not implemented + } + + @Override + public List<GroupRepresentation> groups(Integer firstResult, Integer maxResults) { + return groups(); + } + + @Override + public List<GroupRepresentation> groups(String search, Integer firstResult, Integer maxResults) { + return groups(); + } + + @Override + public List<GroupRepresentation> groups(Integer firstResult, Integer maxResults, boolean briefRepresentation) { + return groups(); + } + + @Override + public List<GroupRepresentation> groups(String search, Integer firstResult, Integer maxResults, boolean briefRepresentation) { + return groups(); + } + + @Override + public Map<String, Long> groupsCount(String search) { + return Map.of(); + } + + @Override + public void joinGroup(String groupId) { + // not implemented + } + + @Override + public void logout() { + // not implemented + } + + @Override + public List<CredentialRepresentation> credentials() { + return null; + } + + @Override + public List<String> getConfiguredUserStorageCredentialTypes() { + return null; + } + + @Override + public void removeCredential(String credentialId) { + // not implemented + } + + @Override + public void setCredentialUserLabel(String credentialId, String userLabel) { + // not implemented + } + + @Override + public void moveCredentialToFirst(String credentialId) { + // not implemented + + } + + @Override + public void moveCredentialAfter(String credentialId, String newPreviousCredentialId) { + // not implemented + } + + @Override + public void disableCredentialType(List<String> credentialTypes) { + // not implemented + } + + @Override + public void resetPassword(CredentialRepresentation credentialRepresentation) { + // not implemented + } + + @Override + public void resetPasswordEmail() { + // not implemented + } + + @Override + public void resetPasswordEmail(String clientId) { + // not implemented + } + + @Override + public void executeActionsEmail(List<String> actions) { + // not implemented + } + + @Override + public void executeActionsEmail(List<String> actions, Integer lifespan) { + // not implemented + } + + @Override + public void executeActionsEmail(String clientId, String redirectUri, Integer lifespan, List<String> actions) { + // not implemented + } + + @Override + public void executeActionsEmail(String clientId, String redirectUri, List<String> actions) { + // not implemented + } + + @Override + public void sendVerifyEmail() { + // not implemented + } + + @Override + public void sendVerifyEmail(String clientId) { + // not implemented + } + + @Override + public List<UserSessionRepresentation> getUserSessions() { + return null; + } + + @Override + public List<UserSessionRepresentation> getOfflineSessions(String clientId) { + return null; + } + + @Override + public List<FederatedIdentityRepresentation> getFederatedIdentity() { + return null; + } + + @Override + public Response addFederatedIdentity(String provider, FederatedIdentityRepresentation rep) { + return null; + } + + @Override + public void removeFederatedIdentity(String provider) { + // not implemented + } + + @Override + public RoleMappingResource roles() { + return new RoleMappingResource() { + + @Override + public RoleScopeResource realmLevel() { + return null; + } + + @Override + public MappingsRepresentation getAll() { + var rep = new MappingsRepresentation(); + var clientMapRep = new ClientMappingsRepresentation(); + var roleRep = new RoleRepresentation(UserRepresentationTestFactory.ROLE_NAME, "Test role", false); + clientMapRep.setMappings(List.of(roleRep)); + rep.setClientMappings(Map.of("sh-kiel-dev-goofy", clientMapRep)); + return rep; + } + + @Override + public RoleScopeResource clientLevel(String clientUUID) { + return null; + } + }; + } + + @Override + public List<Map<String, Object>> getConsents() { + return List.of(Map.of()); + } + + @Override + public void revokeConsent(String clientId) { + // not implemented + } + + @Override + public Map<String, Object> impersonate() { + return null; + } +} diff --git a/src/test/java/de/itvsh/kop/user/UserResourceTestFactory.java b/src/test/java/de/itvsh/kop/user/UserResourceTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..48a49d0eabe39e09004f9727b2fb42fdad9f8572 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserResourceTestFactory.java @@ -0,0 +1,22 @@ +package de.itvsh.kop.user; + +import java.util.List; +import java.util.Map; + +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.GroupRepresentation; + +public class UserResourceTestFactory { + + public static UserResource create() { + return new UserResourceStub(); + } + + public static UserResource createWithAttributes(Map<String, List<String>> attributes) { + return new UserResourceStub(attributes); + } + + public static UserResource createWithGroups(List<GroupRepresentation> groups) { + return new UserResourceStub(groups); + } +} diff --git a/src/test/java/de/itvsh/kop/user/UserServiceTest.java b/src/test/java/de/itvsh/kop/user/UserServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9b683fdc465604b217e3d5bd3023461cc1af870d --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserServiceTest.java @@ -0,0 +1,76 @@ +package de.itvsh.kop.user; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import de.itvsh.kop.user.keycloak.KeycloakUserRemoteService; + +class UserServiceTest { + + @InjectMocks + UserService service; + @Mock + UserRepository repository; + @Mock + KeycloakUserRemoteService keycloakRemoteService; + + @DisplayName("Save") + @Nested + class TestSave { + + private final User user = UserTestFactory.create(); + + @Test + void shouldCallRepositoryUpdate() { + when(repository.findByExternalId(any())).thenReturn(Optional.of(user)); + + service.save(user.toBuilder().firstName("Other").build()); + + verify(repository).update(any(User.class)); + } + + @Test + void shouldCallRepositoryPersist() { + service.save(user); + + verify(repository).persist(any(User.class)); + } + + @Test + void shouldCallUserRepositoryFindByExternalId() { + service.save(user); + + verify(repository).findByExternalId(any()); + } + + @Test + void shouldCallUserRepositoryFindByEmail() { + service.save(UserTestFactory.createBuilder().externalId(null).build()); + + verify(repository).findByEmail(any()); + } + } + + @DisplayName("Mark unsynced users as deleted") + @Nested + class TestMarkUnsyncedUserAsDeleted { + + @Test + void shouldCallRepository() { + var lastSyncTimestamp = Instant.now().toEpochMilli(); + + service.markUnsyncedUsersAsDeleted(lastSyncTimestamp); + + verify(repository).updateUnsyncedUsers(lastSyncTimestamp); + } + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/UserTestFactory.java b/src/test/java/de/itvsh/kop/user/UserTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..21899053598cf2328fef335e27e622169947a351 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/UserTestFactory.java @@ -0,0 +1,34 @@ +package de.itvsh.kop.user; + +import java.util.List; +import java.util.UUID; + +import com.thedeanda.lorem.LoremIpsum; + +public class UserTestFactory { + + public static final String FIRST_NAME = LoremIpsum.getInstance().getFirstName(); + public static final String LAST_NAME = LoremIpsum.getInstance().getLastName(); + public static final String USER_NAME = LoremIpsum.getInstance().getName(); + public static final long LAST_SYNC_TIMESTAMP = 1001L; + public static final String EMAIL = LoremIpsum.getInstance().getEmail(); + public static final String EXTERNAL_ID = UUID.randomUUID().toString(); + public static final List<String> ORGANISTATIONSEINHEITEN_IDS = List.of("0815", "4711"); + public static final List<String> ROLES = List.of("ROLE_1", "POSTSTELLE"); + + public static User create() { + return createBuilder().build(); + } + + public static User.UserBuilder createBuilder() { + return new User.UserBuilder() + .firstName(FIRST_NAME) + .lastName(LAST_NAME) + .username(USER_NAME) + .lastSyncTimestamp(LAST_SYNC_TIMESTAMP) + .email(EMAIL) + .externalId(EXTERNAL_ID) + .organisationsEinheitIds(ORGANISTATIONSEINHEITEN_IDS) + .roles(ROLES); + } +} diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..2eb49ab4e065325a8577d224a12d6091301000a0 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java @@ -0,0 +1,25 @@ +package de.itvsh.kop.user.keycloak; + +import static org.assertj.core.api.Assertions.*; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(KeycloakTestProfile.class) +class KeycloakApiServiceITCase { + + @Inject + KeycloakApiService service; + + @Test + void shouldGetAllUsers() { + var usersStream = service.findAllUser().toList(); + + assertThat(usersStream).isNotEmpty(); + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..967f3f745e7f1699c1b0658e3ec14f4b7c135e10 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java @@ -0,0 +1,78 @@ +package de.itvsh.kop.user.keycloak; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.representations.idm.UserRepresentation; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.itvsh.kop.user.RemoteUserIterator; +import de.itvsh.kop.user.User; +import de.itvsh.kop.user.UserRepresentationTestFactory; +import de.itvsh.kop.user.UserResourceMapper; +import de.itvsh.kop.user.UserTestFactory; + +class KeycloakApiServiceTest { + + @Spy + @InjectMocks + private KeycloakApiService service; + @Mock + private RealmResource realmResource; + @Mock + private UserResourceMapper userResourceMapper; + @Mock + private KeycloakApiProperties properties; + + @Mock + private UserResource userResource; + + @DisplayName("Find all user") + @Nested + class TestFindAllUser { + + @Mock + private UsersResource usersResource; + + @Mock + private RemoteUserIterator iterator; + + private User user = UserTestFactory.create(); + private UserRepresentation userRepresentation = UserRepresentationTestFactory.create(); + private Stream<UserRepresentation> userStream = Stream.of(userRepresentation); + + @BeforeEach + void init() { + doReturn(userStream).when(service).getAllUserRepresentation(); + } + + @Test + void shouldCreateIteratorWithRealmResource() { + service.findAllUser(); + + verify(service).getAllUserRepresentation(); + } + + @Test + void shouldCallMapper() { + when(userResourceMapper.toKopUser(any(UserResource.class))).thenReturn(user); + when(realmResource.users()).thenReturn(usersResource); + when(usersResource.get(anyString())).thenReturn(userResource); + + service.findAllUser().toList(); + + verify(userResourceMapper).toKopUser(any(UserResource.class)); + } + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakProviderITCase.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakProviderITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..800275d14a2644cb81e49184066c347d014a9d22 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakProviderITCase.java @@ -0,0 +1,21 @@ +package de.itvsh.kop.user.keycloak; + +import static org.assertj.core.api.Assertions.*; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.resource.RealmResource; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class KeycloakProviderITCase { + @Inject + RealmResource realm; + + @Test + void shouldHaveRealmResource() { + assertThat(realm).isNotNull(); + } +} diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakTestProfile.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakTestProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..e47cb32c47d3958ff5c9dc266c9ab517a8db046d --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakTestProfile.java @@ -0,0 +1,28 @@ +package de.itvsh.kop.user.keycloak; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTestProfile; + +public class KeycloakTestProfile implements QuarkusTestProfile { + + private final String userKey = "usermanager.keycloak.api.user"; + private final String userValue = "goofyApiUser"; + + private final String passwordKey = "usermanager.keycloak.api.password"; + private final String passwordValue = "S9UEMuLG9y9ev99"; + + private final String realmKey = "usermanager.keycloak.api.realm"; + private final String realmValue = "sh-kiel-dev"; + + private final String urlKey = "keycloak.url"; + private final String urlValue = "https://sso.dev.ozg-sh.de/auth"; + + @Override + public Map<String, String> getConfigOverrides() { + return Map.of(userKey, userValue, + passwordKey, passwordValue, + realmKey, realmValue, + urlKey, urlValue); + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteServiceITCase.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteServiceITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..441f575b4e6bde1860db67f771781b7d72cab007 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteServiceITCase.java @@ -0,0 +1,25 @@ +package de.itvsh.kop.user.keycloak; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.junit.mockito.InjectSpy; + +@QuarkusTest +class KeycloakUserRemoteServiceITCase { + + @InjectSpy + KeycloakUserRemoteService service; + @InjectMock + KeycloakApiService apiService; + + @Test + void shouldCallGetAllUsers() { + service.getAllUsers(); + + verify(apiService).findAllUser(); + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteServiceTest.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f4ed777f9da34bb882756e25b0458e11c36af537 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakUserRemoteServiceTest.java @@ -0,0 +1,29 @@ +package de.itvsh.kop.user.keycloak; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +class KeycloakUserRemoteServiceTest { + + @InjectMocks + private KeycloakUserRemoteService remoteService; + @Mock + private KeycloakApiService apiService; + + @DisplayName("Get all userrs") + @Nested + class TestGetAllUsers { + + @Test + void shouldCallApiService() { + remoteService.getAllUsers(); + + verify(apiService).findAllUser(); + } + } +} diff --git a/src/test/java/de/itvsh/kop/user/sync/LockRepositoryTest.java b/src/test/java/de/itvsh/kop/user/sync/LockRepositoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..182839253272642200ecaa281815df40457337fb --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/sync/LockRepositoryTest.java @@ -0,0 +1,55 @@ +package de.itvsh.kop.user.sync; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import javax.inject.Inject; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class LockRepositoryITCase { + + @Inject + private LockRepository repository; + + private final Lock lock = LockTestFactory.create(); + + @BeforeEach + void init() { + repository.deleteAll(); + } + + @Test + void shouldSave() { + repository.persist(lock); + + assertThat(repository.count()).isEqualTo(1L); + } + + @Test + void shouldFindLockBefore() { + var lock = Lock.builder().timestamp(Instant.now().minus(1, ChronoUnit.HOURS).toEpochMilli()).build(); + repository.persist(lock); + + var savedLock = repository.findLockBefore(Instant.now()); + + assertThat(savedLock).isPresent(); + assertThat(savedLock.get()).usingRecursiveComparison().ignoringFields("id").isEqualTo(lock); + } + + @Test + void shouldFindLock() { + repository.persist(lock); + + var savedLock = repository.findLock(Instant.ofEpochMilli(lock.getTimestamp())); + + assertThat(savedLock).isPresent(); + assertThat(savedLock.get()).usingRecursiveComparison().ignoringFields("id").isEqualTo(lock); + } +} diff --git a/src/test/java/de/itvsh/kop/user/sync/LockServiceTest.java b/src/test/java/de/itvsh/kop/user/sync/LockServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..74ad4fc7e398d99f9ad1470c1c60867a92ca60b0 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/sync/LockServiceTest.java @@ -0,0 +1,139 @@ +package de.itvsh.kop.user.sync; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static org.assertj.core.api.Assertions.*; + +import de.itvsh.kop.user.common.errorhandling.LockCreationException; + +class LockServiceTest { + + @InjectMocks + private LockService service; + @Mock + private LockRepository repository; + + @DisplayName("Is not Locked") + @Nested + class TestIsNotLocked { + + @Test + void shouldCallRepository() { + service.isNotLocked(); + + verify(repository).count(); + } + + @Test + void shouldReturnTrue() { + when(repository.count()).thenReturn(0L); + + var isNotLocked = service.isNotLocked(); + + assertThat(isNotLocked).isTrue(); + } + + @Test + void shouldReturnFalse() { + when(repository.count()).thenReturn(1L); + + var isNotLocked = service.isNotLocked(); + + assertThat(isNotLocked).isFalse(); + } + } + + @DisplayName("Is lock older than a day") + @Nested + class TestIsLockOlderThanADay { + + @Test + void shouldCallSyncLockRepository() { + service.isLockOlderThanADay(); + + verify(repository).findLockBefore(any(Instant.class)); + } + + @Test + void shouldReturnTrue() { + var timestamp = Instant.now().minus(1, ChronoUnit.DAYS); + var syncLock = LockTestFactory.createBuilder().timestamp(timestamp.toEpochMilli()).build(); + when(repository.findLockBefore(any(Instant.class))).thenReturn(Optional.of(syncLock)); + + var isOlderthantADay = service.isLockOlderThanADay(); + + assertThat(isOlderthantADay).isTrue(); + } + + @Test + void shouldReturnFalse() { + when(repository.findLockBefore(any(Instant.class))).thenReturn(Optional.empty()); + + var isOlderthantADay = service.isLockOlderThanADay(); + + assertThat(isOlderthantADay).isFalse(); + } + } + + @DisplayName("Unlock") + @Nested + class TestUnlock { + + private final Lock lock = LockTestFactory.create(); + + @Test + void shouldCallRepositoryDelete() { + service.unlock(lock); + + verify(repository).delete(lock); + } + } + + @DisplayName("Lock") + @Nested + class TestLock { + + private final Instant LOCK = Instant.now(); + + @Test + void shouldCallRepositoryPersist() { + service.lock(LOCK); + + verify(repository).persist(any(Lock.class)); + } + } + + @DisplayName("Get by Timestamp") + @Nested + class TestGetByTimestamp { + + private final Instant timestamp = Instant.now(); + + @Test + void shouldCallRepositoryFind() { + when(repository.findLock(any(Instant.class))).thenReturn(Optional.of(LockTestFactory.create())); + + service.getByTimestamp(timestamp); + + verify(repository).findLock(timestamp); + } + + @Test + void shouldThrowExceptionIfNotExists() { + when(repository.findLock(any(Instant.class))).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> service.getByTimestamp(timestamp)).isInstanceOf(LockCreationException.class); + } + } +} diff --git a/src/test/java/de/itvsh/kop/user/sync/LockTestFactory.java b/src/test/java/de/itvsh/kop/user/sync/LockTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..36ff4f56fa7bacc27f5c725fe756b047420ebf55 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/sync/LockTestFactory.java @@ -0,0 +1,17 @@ +package de.itvsh.kop.user.sync; + +import java.time.Instant; + +public class LockTestFactory { + + private static final long TIMESTAMP = Instant.now().toEpochMilli(); + + public static Lock create() { + return createBuilder().build(); + } + + public static Lock.LockBuilder createBuilder() { + return Lock.builder() + .timestamp(TIMESTAMP); + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/sync/SyncSchedulerTest.java b/src/test/java/de/itvsh/kop/user/sync/SyncSchedulerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..94ac888ded23ad64d01f6f4ec56dde72e1d6a78a --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/sync/SyncSchedulerTest.java @@ -0,0 +1,60 @@ +package de.itvsh.kop.user.sync; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +class SyncSchedulerTest { + + @Spy + @InjectMocks + private SyncScheduler scheduler; + @Mock + private SyncService service; + @Mock + private LockService syncLockService; + + @DisplayName("Start Scheduler") + @Nested + class TestStart { + + @Test + void shouldDoSyncIfNotLocked() { + when(syncLockService.isNotLocked()).thenReturn(true); + + scheduler.start(); + + verify(service).sync(); + } + + @DisplayName("If Locked") + @Nested + class TestIfLocked { + + @BeforeEach + void mockTimeStamp() { + when(syncLockService.isNotLocked()).thenReturn(false); + } + + @Test + void shouldNotDoSync() { + scheduler.start(); + + verify(service, never()).sync(); + } + + @Test + void shouldCheckIfLockIsOlderThanADay() { + scheduler.start(); + + verify(syncLockService).isLockOlderThanADay(); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/de/itvsh/kop/user/sync/SyncServiceITCase.java b/src/test/java/de/itvsh/kop/user/sync/SyncServiceITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..c567157d5a0392e5d43e877c33ccda6f93b9a286 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/sync/SyncServiceITCase.java @@ -0,0 +1,65 @@ +package de.itvsh.kop.user.sync; + +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +import de.itvsh.kop.user.User; +import de.itvsh.kop.user.UserRepository; +import de.itvsh.kop.user.UserTestFactory; +import de.itvsh.kop.user.keycloak.KeycloakUserRemoteService; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.junit.mockito.InjectSpy; + +@QuarkusTest +class SyncServiceITCase { + + @InjectSpy + private SyncService service; + + @InjectSpy + private UserRepository repository; + + @InjectMock + private KeycloakUserRemoteService keycloakUserRemoteService; + + @BeforeEach + void init() { + when(keycloakUserRemoteService.getAllUsers()).thenReturn(List.of(UserTestFactory.create()).stream()); + } + + @Test + void shouldMarkNotSyncUsersAsDeleted() { + repository.deleteAll(); + User user = UserTestFactory.create(); + repository.persist(user); + when(keycloakUserRemoteService.getAllUsers()).thenReturn(List.of(user.toBuilder().roles(List.of()).build()).stream()); + + service.sync(); + + User syncedUser = repository.findAll().firstResult(); + assertThat(syncedUser.isDeleted()).isTrue(); + } + + @Test + void shouldSync() { + repository.deleteAll(); + User user1 = UserTestFactory.createBuilder().externalId("aaaaa").build(); + User user2 = UserTestFactory.createBuilder().externalId("bbbbb").build(); + repository.persist(List.of(user1, user2)); + when(keycloakUserRemoteService.getAllUsers()).thenReturn(List.of(user1, user2).stream()); + + service.sync(); + + List<User> syncedUsers = repository.findAll().list(); + + assertThat(syncedUsers.get(0).getLastSyncTimestamp()).isGreaterThan(UserTestFactory.LAST_SYNC_TIMESTAMP); + assertThat(syncedUsers.get(1).getLastSyncTimestamp()).isGreaterThan(UserTestFactory.LAST_SYNC_TIMESTAMP); + } +} diff --git a/src/test/java/de/itvsh/kop/user/sync/SyncServiceTest.java b/src/test/java/de/itvsh/kop/user/sync/SyncServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cc050f3c1cfd1538de71d84aafec5a8a64b67ae2 --- /dev/null +++ b/src/test/java/de/itvsh/kop/user/sync/SyncServiceTest.java @@ -0,0 +1,138 @@ +package de.itvsh.kop.user.sync; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import static org.assertj.core.api.Assertions.*; + +import de.itvsh.kop.user.User; +import de.itvsh.kop.user.UserService; +import de.itvsh.kop.user.UserTestFactory; +import de.itvsh.kop.user.keycloak.KeycloakUserRemoteService; + +class SyncServiceTest { + + @Spy + @InjectMocks + private SyncService service; + @Mock + private KeycloakUserRemoteService keycloakService; + @Mock + private LockService lockService; + + @Mock + private UserService userService; + + @DisplayName("Sync") + @Nested + class TestSync { + + @DisplayName("With successful lock creation") + @Nested + class TestWithSuccessfulLockCreation { + + private final Lock lock = LockTestFactory.create(); + + @BeforeEach + void mockLock() { + when(lockService.getByTimestamp(any(Instant.class))).thenReturn(lock); + } + + @Test + void shouldCallLockServiceLock() { + service.sync(); + + verify(lockService).lock(any(Instant.class)); + } + + @Test + void shouldCallLockServiceFind() { + service.sync(); + + verify(lockService).getByTimestamp(any(Instant.class)); + } + + @Test + void shouldDoSync() { + service.sync(); + + verify(service).doSync(lock); + } + } + } + + @DisplayName("Do the sync") + @Nested + class TestDoSync { + + private final Lock lock = LockTestFactory.create(); + private final User user = UserTestFactory.create(); + + @Captor + private ArgumentCaptor<User> userCaptor; + + @DisplayName("should get all users from keycloak") + @Test + void shouldCallKeycloakService() { + service.doSync(lock); + + verify(keycloakService).getAllUsers(); + } + + @Test + void shouldCallUserServiceSave() { + when(keycloakService.getAllUsers()).thenReturn(Stream.of(user, user)); + + service.doSync(lock); + + verify(userService, times(2)).save(any(User.class)); + } + + @Test + void shouldAddLasSyncToUser() { + when(keycloakService.getAllUsers()).thenReturn(Stream.of(user)); + + service.doSync(lock); + + verify(userService).save(userCaptor.capture()); + assertThat(userCaptor.getValue().getLastSyncTimestamp()).isEqualTo(lock.getTimestamp()); + } + + @DisplayName("should mark all unsynced users as deleted") + @Test + void shouldMarkUnsyncedUsersAsDeleted() { + service.doSync(lock); + + verify(userService).markUnsyncedUsersAsDeleted(lock.getTimestamp()); + } + + @Test + void shouldUnlockLock() { + service.doSync(lock); + + verify(lockService).unlock(lock); + } + + @Test + void shouldUnlockLockOnException() { + when(keycloakService.getAllUsers()).thenThrow(new RuntimeException()); + + service.doSync(lock); + + verify(lockService).unlock(lock); + } + } +} \ No newline at end of file diff --git a/src/test/resources/META-INF/keycloak/dev-realm.json b/src/test/resources/META-INF/keycloak/dev-realm.json new file mode 100644 index 0000000000000000000000000000000000000000..f20238e3e85e0199d93fbb4c418570d879759775 --- /dev/null +++ b/src/test/resources/META-INF/keycloak/dev-realm.json @@ -0,0 +1,3913 @@ +[ { + "id" : "1964b9ab-c6d6-42cf-8bdb-c9a76523a57f", + "realm" : "dev", + "displayName" : "", + "displayNameHtml" : "", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "none", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "ccc97175-19ca-45ec-a64d-438b63359092", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "1964b9ab-c6d6-42cf-8bdb-c9a76523a57f", + "attributes" : { } + }, { + "id" : "5e95a9f9-eaaf-46f2-8050-c749de58d2b9", + "name" : "default-roles-kop-apitest-verw", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "1964b9ab-c6d6-42cf-8bdb-c9a76523a57f", + "attributes" : { } + }, { + "id" : "6ea8c433-be43-475c-818b-1fd269da77e6", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "1964b9ab-c6d6-42cf-8bdb-c9a76523a57f", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "9e29328f-4cf1-411b-bbe6-ee19f527aec4", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "85d15b74-e658-4cc0-9195-5fedff3e122a", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "ed386119-807b-4074-b1c4-e3ed4e0498d3", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "ea91197e-dfcd-4a09-b251-b7735a48a51d", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "a906d81e-6648-4b7e-8b6b-fe6ae3a8aa6c", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "aff1bccc-3716-425a-bd13-5af791d7b5f1", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "9b9f6be3-5171-4d4c-ae81-6f1293e02112", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "cbb6fa87-ebe3-403e-9ea9-1003a97ffa07", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-clients", "query-users", "view-authorization", "manage-identity-providers", "view-realm", "query-groups", "manage-events", "manage-users", "query-clients", "view-users", "impersonation", "create-client", "view-events", "manage-clients", "view-identity-providers", "query-realms", "manage-realm", "manage-authorization" ] + } + }, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "599fedc0-c913-44d3-b90d-ae9e28d1e411", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "571ca0ed-b44f-4669-aa08-e097a4b3772e", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "c0bc2b45-2a3a-4608-a615-4554889c0f39", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "3bb7c458-44bb-490a-a961-725a7f62d108", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "4338efa1-28e5-46ed-b7d3-faa5af778861", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "57bdcfc7-05db-4ae4-892b-9d3996bae0bc", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "049ddb7a-a0ac-4b33-8825-b23627d2755b", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "b4bf7daf-2a6c-4ef8-aa2d-15c948d49066", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "0da10f4a-d086-49c5-976a-dba273a13755", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "8d41f3df-1f41-453d-8170-b0066e9020f3", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + }, { + "id" : "4e1bd596-4a2c-4024-aa35-ad13d06ace28", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "attributes" : { } + } ], + "goofy" : [ { + "id" : "598141f9-eb6f-4adb-8f6d-d21e1db7abfe", + "name" : "VERWALTUNG_USER", + "composite" : false, + "clientRole" : true, + "containerId" : "73cec8d5-6e03-4589-86eb-8f58e758d730", + "attributes" : { } + }, { + "id" : "7a829180-2acc-47a4-a82a-13d1807559f2", + "name" : "VERWALTUNG_POSTSTELLE", + "composite" : false, + "clientRole" : true, + "containerId" : "73cec8d5-6e03-4589-86eb-8f58e758d730", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "72ce150c-45af-485d-9fc3-f43877fc1949", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "8333205e-d5a6-4d2b-9de9-bd07c9c8579c", + "attributes" : { } + } ], + "account" : [ { + "id" : "ca5a931e-679b-41ac-949e-5468c68a4015", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + }, { + "id" : "8ac3ab69-c53a-49e0-8c70-8ec6bf47008e", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + }, { + "id" : "788bd93a-6208-4135-88f2-3be3ef5ed7b4", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + }, { + "id" : "dafbb791-d2b4-47bb-b33d-d212a90792bb", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + }, { + "id" : "e5bca3d3-b9ae-4e25-b2be-93a11da4ad64", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + }, { + "id" : "a3f2f757-383f-4092-a148-9fb1b9ad1867", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + }, { + "id" : "60db677c-b10e-4b77-8fa9-3f5148cee2ae", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "bf8f95db-6141-419f-adab-7e951659d989", + "attributes" : { } + } ] + } + }, + "groups" : [ { + "id" : "52be1d46-1bae-4f00-922e-02efd6838dcf", + "name" : "Ordnungsamt", + "path" : "/Ordnungsamt", + "attributes" : { + "organisationsEinheitId" : [ "9030229" ] + }, + "realmRoles" : [ ], + "clientRoles" : { }, + "subGroups" : [ ] + } ], + "defaultRole" : { + "id" : "5e95a9f9-eaaf-46f2-8050-c749de58d2b9", + "name" : "default-roles-kop-apitest-verw", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "1964b9ab-c6d6-42cf-8bdb-c9a76523a57f" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "users" : [ { + "id" : "26c16229-0f37-40ce-b44d-27bf1b63141a", + "createdTimestamp" : 1660890501831, + "username" : "alice", + "enabled" : true, + "totp" : false, + "emailVerified" : true, + "firstName" : "Alice", + "lastName" : "Wunder", + "email" : "alice@dev.local", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-kop-apitest-verw" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "9d7633d2-ddf0-4d18-8384-5c9581bc4635", + "createdTimestamp" : 1660890535597, + "username" : "bob", + "enabled" : true, + "totp" : false, + "emailVerified" : true, + "firstName" : "Bob", + "lastName" : "Bobster", + "email" : "bob@dev.local", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-kop-apitest-verw" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "0592dc73-cb01-405a-be10-cc6a204ecf1c", + "createdTimestamp" : 1660890234315, + "username" : "goofyapiuser", + "enabled" : true, + "totp" : false, + "emailVerified" : true, + "firstName" : "goofy", + "lastName" : "client", + "email" : "goofy@dev.local", + "credentials" : [ { + "id" : "8cf11911-615a-4fe4-ae0b-37eafac7d8ff", + "type" : "password", + "createdDate" : 1660890249119, + "secretData" : "{\"value\":\"wF2IlMsSnwFvz2I46UOIjeqe0HKzbRIp8s5mymeVxF+H0iU6bVm6qBYT1PXYT3v45AJFPSHtQpG0lA6FxN2F4A==\",\"salt\":\"Cw1vL4Rx1rOwgB6Qn15Pyw==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-kop-apitest-verw" ], + "clientRoles" : { + "realm-management" : [ "view-users" ] + }, + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "c970c2c4-ec0b-4f1e-8a58-769fbc8518f9", + "createdTimestamp" : 1660655234752, + "username" : "sabine", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "Sabine", + "lastName" : "Sach", + "credentials" : [ { + "id" : "98297f4d-3aff-4407-9c82-5c25b4e0406e", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1660655259975, + "secretData" : "{\"value\":\"rbnkjLAt+dxo9k7F8hHKKZ8EN4kMPLHVAhTTcAHLtE7e2wLgc9qnqelAlQeWZZXwXSnHDIxHjqjVW968JPVNTA==\",\"salt\":\"qEBm+fok7trlVRYPHrQfJQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-kop-apitest-verw" ], + "clientRoles" : { + "goofy" : [ "VERWALTUNG_USER" ] + }, + "notBefore" : 0, + "groups" : [ "Ordnungsamt" ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account" ] + } ] + }, + "clients" : [ { + "id" : "bf8f95db-6141-419f-adab-7e951659d989", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/kop-apitest-verw/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/kop-apitest-verw/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "97f52335-14d4-4db6-a120-a0a3e5b96dc4", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/kop-apitest-verw/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/kop-apitest-verw/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "92b4e98f-126e-4734-bfaf-f2aa2b1c665d", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "3653296a-de2f-4faf-b72c-ddbba60fa529", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "8333205e-d5a6-4d2b-9de9-bd07c9c8579c", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "73cec8d5-6e03-4589-86eb-8f58e758d730", + "clientId" : "goofy", + "name" : "Goofy", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "http://goofy/**" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : true, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "tls-client-certificate-bound-access-tokens" : "false", + "oidc.ciba.grant.enabled" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "false", + "acr.loa.map" : "{}", + "require.pushed.authorization.requests" : "false", + "display.on.consent.screen" : "false", + "oauth2.device.authorization.grant.enabled" : "false", + "backchannel.logout.revoke.offline.tokens" : "false", + "token.response.type.bearer.lower-case" : "false", + "use.refresh.tokens" : "true" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "472c7034-421c-4634-9ae0-e5a9951158d4", + "name" : "orgaIdMapper", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "false", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "organisationsEinheitId", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "organisationseinheitId" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "87aeb7a3-5be5-486a-93b3-7fab9f0f55e3", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "2b73fff6-0e4b-402e-9242-9a5ccc8b7281", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/kop-apitest-verw/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/kop-apitest-verw/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "6f7a8a07-e8ed-4721-b2be-e38d7ac34877", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "1a510ca2-38aa-4667-b0dc-8c9dbda92f21", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "7f215868-55b7-4442-bbd0-7373cf08ed83", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "6b09d606-3bed-4f41-a4d2-1df0a071246c", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "434ba9b5-8a99-4aa6-b609-6604b06105eb", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "80ebd080-acba-4f6d-934d-a4beff935fce", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "24938eb7-e2ff-40d6-8ce4-27e4ff2301db", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "cf37db2b-fbc1-4fd9-b674-851f70064434", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "ba257d57-f147-4c06-ad0a-6ea174fd5a34", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "9ce3f4c8-fe15-4b31-90aa-1382a1688e31", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "cd3cc8d0-113b-47d1-aa12-56cee9c5db78", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "4119f432-10e6-4656-aef8-bee2917f355b", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "59c7a142-532f-4759-b487-f858722a79c3", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "ba4f7ffe-5b2c-4c20-9806-541b3f0605a3", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "9f32604f-2419-4c9c-8b41-4a83559d3de4", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "16a358c4-29ac-42ad-856a-b4444e93826a", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "78e09dfb-a7be-4eea-bd06-3f7bd871235b", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "f38911b8-b4dc-4851-ad33-9f9b6dfff4d6", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "f755d172-09d2-4a6e-8c6c-86bfb09ba4f8", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "0fd832a5-c891-473e-bea5-443bbabfa2e4", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "40b5cdcd-d7d4-4174-b26a-4a9ec8caafa5", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "03b88607-446d-4ac1-a08d-7ba6e0cca404", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "9ba9e659-dfa7-45de-b966-6fc4f2f7db29", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "b857001f-4a72-4a97-b157-bb58b4c4eef9", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "30c69082-2d00-436e-a86e-2419b573c074", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "e038046e-356b-42b8-bb10-8435c66a24ae", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "44ec36e2-cb7c-46e7-9955-57b9b1e59f1a", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "31b90dab-111c-465d-b703-847c8103db5c", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "2acde2b0-3272-40d7-8c2e-0acb4483fabb", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "13536367-e731-41ed-be86-eb3bcf68c503", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "cb107c43-10a7-4794-a677-3492b24f6c2e", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "73a14943-3e56-4092-9fb9-f9a35146c9c9", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "81d10ca9-5412-4253-8bcf-93364345b64d", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "1809e1d2-479f-4510-bda6-43c1ea1f98b2", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "0483a09e-a5ef-4e8a-bae4-ce801423e065", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "7b0c2a1d-7674-4b3e-92f6-9614f65e06a3", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "4e487a9a-54b8-4361-985a-080c196ba714", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "7f02b25b-3f3a-4a90-8152-557a263b5eea", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "web-origins", "role_list", "email", "acr", "roles", "profile" ], + "defaultOptionalClientScopes" : [ "microprofile-jwt", "offline_access", "phone", "address" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "4a3058a3-8aa4-4e42-98d5-d51ddc7f8215", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "f2fdaf71-797e-4901-8889-02a05ec6257b", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "775070ec-69b2-4faf-ac87-7a77115f8649", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "5543c38f-8399-40e2-a70a-082f96de18a5", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "5088e788-f3e6-4dc2-8fe5-c464d5d527f5", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "e7d9c16b-7dc0-49ec-b121-e2d6a37481cb", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "cda5c120-5627-4d88-9aa9-c6b64b8fcb3c", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper" ] + } + }, { + "id" : "759d5b40-45b6-4acf-acdf-d9ebf49576dd", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "95cfc09b-6e7d-4adf-8e9e-3b7a02f02b67", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "4bd3752e-313c-44e0-a511-0977b9aa002b", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "7fcf5141-b805-4db2-8b79-6b14caff09fd" ], + "secret" : [ "cBWGKkHpHRRT8EDIn913r4cnjyS_sSo70h5-cMb8fOjvtCzce3ujnZLaXzyIfYZGjEF6d4_oeDtMdBtie9u61g" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "0ffe2318-6296-43c6-aa58-498303905d6d", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAmcaRhxoBQ+bGtC0ZoEEsQH0tFWV58iJBl9cJzRjVD4UfDEpMP40nkXMpH/vhMm1bQAClViOcqEctv1QQoH7zIDFxH694vz3ymlot3wCIV3rcCrq2cb9wyq4CIccH+bswL8/TLrYd5a0W3BmNpGNrTqFiB6GcuJhfoD+bq4wD51lwInN9h/tKHhtWxySuQkTPQm75Tu9Phxlqac4pgFCBacCMG2tVSBUIJBNT4FS91y05mH1Wuu4CILyo6AQ0socB3K+7lc0hY7zC1cyQyaR2JGM8n7wFUazVdPZBp8bffKxHzfpPscY33Dt2IhP3rILSS+bTjvoJh98HaO2S31L+JwIDAQABAoIBABxXk9Txvw/zxeXJdjKqMGzGZXvPEzXEiJBiyuSpUgOlRn6uZvVDn+pX7ll9xBjWPP5D4DVLJQGC9PSRmurp2wew9Seg4MFmuoMmYdP7NiXheBLmLOA0J6bDgZMWKuslTBFT3Zl7aXE+gLsaXpfQ1yNL7VR4vfjr5JwVfSyWBtYpxYUwfRmVI/c2gSlJYU0s5et2X0eNdwIgQ5+NakI3mo5zlM5eYQSuK1VuhAA+hGWmz9bPzjMk24hofNkODuwPWHv4JtR5c7OLcwo9op5Pc9X0KHH0pjEW14Ir5g6umu4Q9WmrZnREgKphrODZ0DnPl/k292T5AtZEvrFpRFqQxyECgYEA1RAjCuvqoLY0BiN7zk3Me2a30YkDA0y0ctYVWAAHjP1wqN8dFS8r7xkIyLZRj15EPM3IhVQnqcjtQEUAoaV9n9KSmBbbZqyOoOntMO5phIb2TsIzwDAMNB23NJd53vPN87TeBG1JdnzjXiZsMFDkB353HXa7b4lqnbQh/4/KofcCgYEAuMPP7By6Q6AvMktP+bxt6116nDE7kmjTArqXMBNfd6drLgeSURKBi2nStb9gGlA8GWPnFuWjw35Gybw8kKdM+sw8Uan2MpEMokcTjnvgt/r5oFtxVdEBaOFWJb39vtc+2yySBeLTweIv/U9goSdGgNyBpkkRBIeAtMV5wEmYeVECgYEAilQIvE2Arki9LAMbnUyLZs7ApyySJTFGsovlnZWfUV02KJ16b0eJ+ZjCSDV+bFtiaCuedVm2ypel2SUzjL85+WqoPFASr+12SGi30x3mqeFJxsZ0/OD0+10TlfSGbkbRvtj9j9g4atIYeAbwFMpf0bG8ugddF8Qa0TqjHB4KC+8CgYA9OQUoo8xEpEt/St1RT1LM7si2AMpQlVN2UAXQ9Fpp95vYQMIHAy7R11ruxl892OBROX1VZPqCyNED/8/Bzu3/HLXQCZV+4/lfvFWKnRm1XQBiVmqTmRHygthc4Tu3hPNbBMXOFX89e4mTUj4eqDkAchCz3Po6mfvX6qeRQ9G2QQKBgFtKWV6t/zEJ+5PWNc1u7sMllRi5HVhuqjhIY1N0nqSnwyn8p6bs5OUFlaqvXXO9cpsHDrLhO4ioT+Frs6EJIFQoPlU2pVzFXGRsaRq44qG5f7KQ43irSoRMOoJToHoGxh0E9dq6Bo+Jkg3u242l8GwZL6xZw/TSRKgwGsKoD6Gi" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICrzCCAZcCBgGCpsFoxDANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBrb3AtYXBpdGVzdC12ZXJ3MB4XDTIyMDgxNjEzMDI0OVoXDTMyMDgxNjEzMDQyOVowGzEZMBcGA1UEAwwQa29wLWFwaXRlc3QtdmVydzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnGkYcaAUPmxrQtGaBBLEB9LRVlefIiQZfXCc0Y1Q+FHwxKTD+NJ5FzKR/74TJtW0AApVYjnKhHLb9UEKB+8yAxcR+veL898ppaLd8AiFd63Aq6tnG/cMquAiHHB/m7MC/P0y62HeWtFtwZjaRja06hYgehnLiYX6A/m6uMA+dZcCJzfYf7Sh4bVsckrkJEz0Ju+U7vT4cZamnOKYBQgWnAjBtrVUgVCCQTU+BUvdctOZh9VrruAiC8qOgENLKHAdyvu5XNIWO8wtXMkMmkdiRjPJ+8BVGs1XT2QafG33ysR836T7HGN9w7diIT96yC0kvm0476CYffB2jtkt9S/icCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAArGwmbI8BvL9BbnxZr+E2lOcCnPVctTreuQGECP69WvmNqpMzfClESvvLdq7al54s3WLfXqI1L2Q4FEMw1AmTQtuGsIwYUn8DA2VMiU2fHV5YxL99yNY2iKenWqvOgC4rWu6+Jk62dosUYo/Rk5a1wvHhIQqORVerfHnZqEcT2SGtdFUioA2KiKvx9aEGa7xbcYqWpZCZ0hBwM6BSmdGGTV2sBOXulDsKl6rf7jZJbg3EVW7MUabDIcII6askYeQP0DvC/qEutCH/x/Ei6YDOF4nMGpvJUqN4v3aDDfqwesO0mfgY143qr8iG9WI8wyOahZ+Z8RJTaAmqIVIM97dQQ==" ], + "priority" : [ "100" ] + } + }, { + "id" : "17db465e-c349-4b34-874d-a4671e68206f", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "abc0b43d-3f75-4927-a38e-8f579de7ad20" ], + "secret" : [ "gcvgiVD4NA0jTxMiU2LuTw" ], + "priority" : [ "100" ] + } + }, { + "id" : "8d29912c-0f8f-4c64-823c-9992720476b1", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAtORIUr4zt9vlb5wXm6jBAqekzRuYnaJwoka/NrN+x1N6yDBzAn8mFFGMXsKJENHJc0Vt0JkXvjjiqFBekttIzODfgEh7ZBvg4gK9r7XR++jA/gXZetlg5J8L6WToLVOPm0MZ0Df07WAFtF/z03C7gsS0wd3Nhp/VGzThdjvLuq0XVMythrTZGh7V19IvahawAxDA0vWxqOK3MaEmlArFEpz2erEgbGq7KW23In6fUqnpLBSGMVULPmiv/8FYtRr66tOHYopE6kuqk+OUaX+ih+5/ATVdvA3XM0h5fgxkXj7eBLlpJuuy3ejAn7XPSjfULkKSzJseM9dHlTtVARntvwIDAQABAoIBADRS+CyfAfzD6bkAEMAg7zuiXIRL4DhwvV3jUvoyr5BesFxpz0rAlrY35H+gi99Gn5vtUFePgROwBrgjD0gxj/xba6sCzFZnzgPyQQYrdMMGT5TrAj6L1IAtFVf8rUA3NIn3vebB2OI07VGAk9nseQDZf2O4kfPIFe+Zu0HlCAhF/oO8Fg2/nH7DQRdtO3gTnDfJnpQiV7IE7wKB/1v/M1ebRj4gv5yPq39XA1pNHtBmZqneyhmz+9f7ml1LvqJ+LPrhYmqphlVJzShSsi1kbKfuD3nbRql8xg/p+xIeiZ1yCeEZflh3ovw9YYe4Ehs4IivJk6FRq4OjK3K4L4nTAcECgYEA5VKK+7IQAo+L5Q91c8zGuBi6FsmvORN7rkhXGFmzOhiYTi7ihzZcM+1vkH+Owgt5Thc534QPNN2RgZm7rW9iMzCQyMLA17OY85/Hp7GKPdv1L14RrWfEdRUO3Hw5EtZJjzfaAdaSvFX16zwdEvKh5447O2RCMxNFIyN7k0fhkaECgYEAye9scD4kwq9c6XsuXFZ+usov5u4h/5SmuG77MUxqpZUlitMNWzIKzOAoMZx2SLlu0al2VBQGhanyTnxpJGptzFhj+X0RdVUNyAH63AommiFbStueFZLi7sFo58de8UXmb2y3p5FNnQNYbdnDoKeN9w0sZDYGxj40FYmCCfGwA18CgYAGo0oEmRXfjRomijkDHhVOdODGEhZxV4AH/m4O9WG7t8SI1tCVy1wF4MSO8TJHqZ9cRE3Xi3IJPLSehL2q+oNkKe628PQ2CivOOXzE/N2chsZlFZr7YvqLzQ0+EpZwViWQ8LPtw7FBpFdW9Ml+p+lJ05MhV/iWk+M3biqAi98aoQKBgGsOxSXIvIJ+qDqcdNq9fJiZAsZir6GpdK5JIGFlixDYPrwX4nfb0wAFQynyMn+h18/OEQ7IQyDwFadoq/GDf0yb+/zUQsI64oCMFCw+MXL7hkPspSX45tj/rxdaispNbbrbH472KPi6QlKS5fUxf8FfuY4zWmoVplVYvpl5CgDvAoGBAKDg+imoQPMREFXhtSwu7qJNx6vu1By2RfnjffcCBXJjf81uGDfn6zE9H1WdbazxmfSU5zqY5aC7YQ+ilkwKw3O80ScNHqkXYqEdNKtbwD5BpJJxGo1sQkmY+gb6bqV0rTjMI0n2qb15K9OYMFvBcQoVJdmMqeFLGTMciiqyMXjN" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICrzCCAZcCBgGCpsFqfjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBrb3AtYXBpdGVzdC12ZXJ3MB4XDTIyMDgxNjEzMDI0OVoXDTMyMDgxNjEzMDQyOVowGzEZMBcGA1UEAwwQa29wLWFwaXRlc3QtdmVydzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTkSFK+M7fb5W+cF5uowQKnpM0bmJ2icKJGvzazfsdTesgwcwJ/JhRRjF7CiRDRyXNFbdCZF7444qhQXpLbSMzg34BIe2Qb4OICva+10fvowP4F2XrZYOSfC+lk6C1Tj5tDGdA39O1gBbRf89Nwu4LEtMHdzYaf1Rs04XY7y7qtF1TMrYa02Roe1dfSL2oWsAMQwNL1sajitzGhJpQKxRKc9nqxIGxquylttyJ+n1Kp6SwUhjFVCz5or//BWLUa+urTh2KKROpLqpPjlGl/oofufwE1XbwN1zNIeX4MZF4+3gS5aSbrst3owJ+1z0o31C5CksybHjPXR5U7VQEZ7b8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAOcMuNdJDra77bySvC/5/oIqpWUAFsM0ssUTBpuVe2pc/8JcoSFLwsOZn8uBb8by+qPyYAQmJ+CnsSgicQaW8inYraeE3KUA+VI0D5U0FPq6emI7hJXczRj2DtlNE7Mvp7aXMkuZp0xlKzktyUg8EjUYd21mr678EwjOdOvgUMtPLZKvyyCpvE6OVXSsIZq10+4i0UdJyR2bD6sqOcdSapAwj3kqwH6+p5EHQL0Km+JFR4eyPedANm6kZXp6WfxMHHziWvT8WupfCIw/pjtRGXoyn31+kAzs0O6LtCeFW5yFgIPwL+6xbtM+ThFUyhNg1TGny0/fMRfLptrvriinotQ==" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "8b4babf7-5560-4470-94ec-be1e391a5a1f", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "9ab76242-a2a7-4e7f-b0c7-679eb20a8d8e", + "alias" : "Authentication Options", + "description" : "Authentication options.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "basic-auth", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "basic-auth-otp", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "e7e84029-12ae-43b8-8d7f-53178b711829", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "056e9e36-9ad5-4113-90a6-a3f84df31fc5", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "e3f140c8-ccd6-4002-a3d9-fb4acd9e5812", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "078dcee4-009f-4da5-a11c-d89daf371a6d", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "45dcb4e7-3d3a-4dd7-8a9f-002512e1ec24", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "246f0b4a-7837-48ce-bdea-239c5c0f3d56", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "1d6a6368-4743-4187-92b5-d7c75800737a", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "db3b1215-d705-4419-8268-546d49178ca2", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "df2bb008-c488-42d6-95ef-6e5b4416f933", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "e753ea55-9489-4039-91e4-1504d2726fc2", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "f8b06555-3bdd-4f40-91ac-f5bdd6993a40", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "1ec385b4-b7f7-466d-8a4d-e996a34aa72f", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "cad74125-0f95-4bbe-9448-7a9bb9eb01d6", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "615612e6-0085-4a94-8fb3-7b616b0dffab", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Authentication Options", + "userSetupAllowed" : false + } ] + }, { + "id" : "fbeac131-7098-470a-b12c-552166b90c81", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "17ff9178-b88b-4646-a32e-9edc28bb714d", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-profile-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "86b9197c-c1eb-4354-b036-d573d2407adb", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "cac02a4f-7918-4ad4-aae6-f2948bfbd218", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "e3b68ed0-580e-45f8-a36e-21f169427f58", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "6b17fb91-bf25-4ddc-8dc9-85d53ca0f71c", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "frontendUrl" : "", + "acr.loa.map" : "[]" + }, + "keycloakVersion" : "18.0.2", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +}, { + "id" : "e81ed749-e62d-4dd0-a200-8a1406152032", + "realm" : "master", + "displayName" : "Keycloak", + "displayNameHtml" : "<div class=\"kc-logo-text\"><span>Keycloak</span></div>", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 60, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 600, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "c03d1d72-4237-4da2-b4a8-afe18ed88907", + "name" : "admin", + "description" : "${role_admin}", + "composite" : true, + "composites" : { + "realm" : [ "create-realm" ], + "client" : { + "master-realm" : [ "manage-realm", "view-events", "view-authorization", "manage-users", "impersonation", "manage-identity-providers", "query-realms", "view-users", "view-identity-providers", "view-clients", "query-groups", "create-client", "view-realm", "manage-clients", "query-users", "manage-events", "manage-authorization", "query-clients" ], + "kop-apitest-verw-realm" : [ "manage-identity-providers", "view-users", "query-users", "view-authorization", "manage-realm", "view-realm", "manage-clients", "manage-events", "query-clients", "query-groups", "create-client", "query-realms", "view-events", "manage-users", "manage-authorization", "view-identity-providers", "impersonation", "view-clients" ] + } + }, + "clientRole" : false, + "containerId" : "e81ed749-e62d-4dd0-a200-8a1406152032", + "attributes" : { } + }, { + "id" : "000d8005-a3ec-4f47-8e97-23d644a117cf", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "e81ed749-e62d-4dd0-a200-8a1406152032", + "attributes" : { } + }, { + "id" : "0162b5b8-5461-48c7-b253-f4ceaf603b1a", + "name" : "create-realm", + "description" : "${role_create-realm}", + "composite" : false, + "clientRole" : false, + "containerId" : "e81ed749-e62d-4dd0-a200-8a1406152032", + "attributes" : { } + }, { + "id" : "5ca51565-71f0-47ce-9372-287c9bd54f5a", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "e81ed749-e62d-4dd0-a200-8a1406152032", + "attributes" : { } + }, { + "id" : "c4200c9d-0478-4709-9981-7240d7bb550f", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "e81ed749-e62d-4dd0-a200-8a1406152032", + "attributes" : { } + } ], + "client" : { + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "4e01b517-610b-4f9f-9d78-bf97f0bd854f", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "d0de7847-0f54-4933-8796-7dbf3c6042a7", + "attributes" : { } + } ], + "kop-apitest-verw-realm" : [ { + "id" : "ccd6f26c-0400-4dde-85bd-d80a705f6515", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "d5662b9c-adb8-4ece-a7f0-30347024de44", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "59d5a221-ebcb-4539-a642-f32c2ab8a1fd", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "9f48bcad-dab5-4e67-879f-922b654e574a", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "be8bcea6-f795-49d3-abb7-627026f85d28", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "24aca00d-d3a2-4503-98a3-9073705748b9", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "kop-apitest-verw-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "3d68d987-f0fc-4781-838d-7baf5e974659", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "2e3a35e1-0356-4a26-b6ca-f6e5a3b0cfbc", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "29530ef0-2018-4156-829a-acf9b5ff5da2", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "7823b717-dbbc-403c-9713-2db3eb784a08", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "9e3caf25-82b8-4e84-b19f-cf4519054925", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "90dba6b2-d628-4824-8e02-8208dfe3f162", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "1d5929ae-1dce-421f-9a5d-7aca1a872e52", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "003b3af2-6856-4c5c-a005-153770863fae", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "6875fd3c-4c51-4780-85e9-6a8a4fb7fc20", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "4b5f482e-8b78-41b3-b9c8-aaf6e76819e1", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "29d9cfe0-ecc6-4c99-8300-d82465beb7df", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + }, { + "id" : "51733859-c072-4f9d-bc7f-fabeca52a34e", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "kop-apitest-verw-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "attributes" : { } + } ], + "master-realm" : [ { + "id" : "332b2182-e47e-46b3-b0d4-27c809ec9166", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "929317c0-4e94-4044-b51f-833f55768bc5", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "5867e198-5144-4b8f-a77e-79defd2b7ebb", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "773c6077-175e-4da3-8276-f86b360615a8", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "2a669505-d12e-407a-be22-f2a2a2ad3050", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "20efe9e9-469e-43d5-8d9e-d0132586db6d", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "a82d2a8b-3b61-43cd-9ccf-1b1bd99e5964", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "cf8aef6f-6d68-46fe-a2a2-f5b964be8363", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "1b98e3b5-3c33-4db2-a7a6-45fc7e1578d9", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "d8782c70-31f8-486c-be81-cba0deebe000", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "27c1c898-50ee-423e-bfcc-968bb13b2402", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "df36f0a1-eaea-4921-905a-cd9ce5221135", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "5168578d-2aa3-4723-a57e-421d089e24f7", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "9890dd8c-642d-45d2-8826-7bd92f21d346", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "b5dde334-ef65-40ed-8515-032a77ea76f3", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "1dedc1c6-9b01-420f-852f-a5c4f42bbaed", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "076a68b1-77ab-4b98-a86d-55524917e713", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + }, { + "id" : "a8ad610f-7f11-4c3f-8966-01162b0f04b4", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "attributes" : { } + } ], + "account" : [ { + "id" : "20c66340-95ee-4372-80df-f2071ad9cb25", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + }, { + "id" : "c3fffedc-6641-42f4-8fd2-820195c73a4f", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + }, { + "id" : "deaea61a-7681-4f26-af62-8ca8f63d3d70", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + }, { + "id" : "0683711d-34d5-4966-88cc-87c7e57db7ba", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + }, { + "id" : "ce17b81e-17e1-47d2-8cf0-c1d39088af0c", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + }, { + "id" : "9526bdba-d1e9-4501-8e3b-12faa10db6b4", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + }, { + "id" : "10f559dd-a86f-4fcc-964b-189a8c050884", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "000d8005-a3ec-4f47-8e97-23d644a117cf", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "e81ed749-e62d-4dd0-a200-8a1406152032" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "users" : [ { + "id" : "6314093b-975f-4e83-a1a5-81e5ebaff3fb", + "createdTimestamp" : 1660890109214, + "username" : "admin", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "credentials" : [ { + "id" : "023812f6-e9d2-4588-84eb-db5676579068", + "type" : "password", + "createdDate" : 1660890109412, + "secretData" : "{\"value\":\"kwvQW91WpNbg6SwNerhqSNb1fmmR1sut54bGXEOiIu1MaZtB4odDivEPTSzLLx/b5QN60Ek5am0avh180Wl0Rg==\",\"salt\":\"ARAVPQWDMYd1E7Eot8wPtQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master", "admin" ], + "clientRoles" : { + "kop-apitest-verw-realm" : [ "query-clients", "create-client", "manage-identity-providers", "query-groups", "query-realms", "view-users", "view-events", "manage-users", "manage-authorization", "view-identity-providers", "query-users", "view-authorization", "manage-realm", "view-realm", "manage-clients", "manage-events", "view-clients" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account" ] + } ] + }, + "clients" : [ { + "id" : "816a0f6b-5f92-4014-af04-c9c6445c969b", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "e82aa78d-b4d4-4b9c-b305-b36c1c29e274", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "6bceba1e-e804-4bab-9fdd-9183f8194379", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "851c207a-076b-45af-af0a-df374c43dee5", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "d0de7847-0f54-4933-8796-7dbf3c6042a7", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "80711f4c-5abf-4fb1-8dc8-f500115011dc", + "clientId" : "kop-apitest-verw-realm", + "name" : "kop-apitest-verw Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ ], + "optionalClientScopes" : [ ] + }, { + "id" : "820e5013-a22f-402b-bdc0-d382d9e5bc64", + "clientId" : "master-realm", + "name" : "master Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "5604a389-a474-4e56-a4ba-7af9209643b4", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/master/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/master/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "9dee6645-3070-4af0-bb4a-6bfa356827ad", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "9408ce58-2a52-4b1c-b4ff-515e91e9efc3", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "94c500cb-0358-477a-968f-82a5aea6c6b3", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "388bc69d-1f54-4385-8c1a-e718ce4ca542", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "ee33d532-8b55-44b8-93e2-523ac9e60b38", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "26004e1a-4aae-40d8-b85c-91e120efd172", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "119dd2f4-f91d-418a-a1f5-a31bf9f584cd", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "19d30839-7a1b-4216-b12f-6c65284717d8", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "c1b516e2-e7ac-436e-b876-da22083efea2", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "e472ee44-5b9c-4ab1-82d7-fc9a2b62bb71", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "dd5e2bf8-61d6-4b42-b06f-0330f32356a0", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "755ff7b9-dd91-47a3-b8ea-1fd0ce439dae", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "ae39ed0c-07fd-4bff-8d52-af4ea1daae2e", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "7beceec4-c579-440d-ae1e-934aab4383c0", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "82e09e62-6449-41fa-8192-a5610e8fce69", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "111dfd6a-a2c8-4884-9bb8-8d2120aa5138", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "eec97836-6bd9-4073-8c97-e76d4ce2b749", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "2d39ee08-9ae2-46ee-92dd-0f58b03ef86f", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "fda1318b-802a-4711-990f-b8b4e52ac45b", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "d0a14859-586d-42f2-b0ba-1956348c30dc", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "b2ff65b7-8606-4440-99d3-1dc509cd0479", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "84d8cbfc-f5d0-453f-9b72-84b1a443864a", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "9d670b5b-5c37-4af4-b832-75a14fff3573", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "5935fb83-737a-456d-9bd6-45ec5d1ef8c4", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "a044140c-f4b0-421a-9e2c-1a9c9237ff4a", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "2bae9756-deef-4e5b-acf1-7ba58fb82e71", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "c61b1f2a-1c31-41c4-89dc-326012c0d0ba", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "3ec0a858-4e22-4330-acc7-ec8b34797062", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "16f6505d-36f9-4a88-92a6-1e2602822531", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "5149261b-6018-4afd-8635-09ee0ff905c7", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "2b7a0ce8-7cfd-47eb-9a34-b08787dc9eff", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "c722b9d1-c67f-492e-b75d-e614f791d7cb", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1d8da349-319d-465d-bd9c-dfa731b6efde", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "c3c1ae73-ac3c-4a53-9cc1-c78ed646722f", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "08f3e0e4-f228-4c5b-b6a3-d960e689be1a", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "16b2017f-dd76-4ae1-84b6-0ea63ac4e89e", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "f356a880-f40b-4c9f-9b0d-064ff3925734", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "8cbcb253-b64d-4da8-85d7-29ef50e9a657", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "web-origins", "email", "roles", "acr", "profile", "role_list" ], + "defaultOptionalClientScopes" : [ "phone", "offline_access", "microprofile-jwt", "address" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "xXSSProtection" : "1; mode=block", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "8b89de8d-1fef-4a65-b4ee-c402c803d187", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper" ] + } + }, { + "id" : "adafd8a4-151c-43c3-bab2-98bd9d3c8b89", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "94c0848a-617a-4706-bc42-102ca2b2496a", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper" ] + } + }, { + "id" : "c6cc93f0-7092-4ace-b012-c3813d4edff1", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "d2bc0e39-1428-434c-925d-d558845e1697", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "8b4ae70c-67b7-4cb8-801d-8b5ab357b4d2", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "d5961ff8-c1b2-421b-9000-850fd5d000d6", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "7716d0ff-3c75-4158-8d79-410f15586d49", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "467045d7-ad14-4a25-92ca-047608aa0cfa", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "cd753a25-9bd0-4643-aa08-091dcab19bdc" ], + "secret" : [ "ZMruEVEqsgkD6OD8f7oFpokHIW_wwmwUnNX9vbDv0COuuhxA3EUZ6KY9ihiKdkB7SWXXuYRJhXSe_DEJB5yMBA" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "2588c88f-2b4d-4461-aead-64ef172c1101", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEArGeuuZIo0ihMH8aba6PIiV97w9YqZl+gWmuSRUIZaQ+FhzSe2AKQb6FGrdyEE7y3rj85x1RepxMhyKMZiq9vX/KgA/TsShmjAW2zuwnoJzoM5xyzsWolu2F8jrOo9NwDVG+LzSankfov5QsTgGw/Ndb2v1YrlpNKeke7vwBle3lj/XMdgcTel8fXSQtdsHpNz11GlK7g1WjkmbuoRdgkxtoCtqkwFlLRa4AzqQip4cqW2ej7/nLHQcoW4YMdfMsFsWXjk6y7mK7DzrytVoqy2RTCKg/MFho7Q0eKLSHtkivBJnBIBAFf7Rf0RNEo4kLU1HBep1lH8vxPVRp14cS+rQIDAQABAoIBAGNiJXu+AKAdMLzEjYWAVbKzAzKCO9Dl4RcemaQpFWLV2sB1GfglikN/cbH5w31pNW1R7ymzvhiL09bIBopG1Wo2//0n7CpPGwNQzOHONTCfqx5zEjb8nKGm8dmFQaldRuIepzVsyf5BfeiH+Qb333xW3ciwkNHtOGrC/Tx+qwGApz04Jh2fb2jx4xcOlRfXT7kpM5phqwVE8QWSW242TJZn1CFL2VKu/s9UFF/qJf4Ds2/o89HTkzklbF+CkFBpBGvH/7ZQ7ePB5f97qSck0V/Nxyo4DQMnLIYMZSfHWjg1fsrmDRd5DHkeGn+Jx9srsvIs1wSO0vzJBPRMq3shOCECgYEA2gpgnTrfhTbMytDz9n3WrVdwmlTNV99c5iFLxRB1Ik9ANXkp+Zm9GazPHfjSSDPd5g2atRWk4K6a2dpxRr+2rJtYMeRyMApXC60Uk5+jQrHtVn98K6unybboJlJoA7sFTxLHAJx7FpEjqokUpLKX0Wualx4Jpz2uVK2ZM5OpLNUCgYEAymtsMjBf6vGVdzc89n4JKIoGPcJBFY4iH4/rxVyw20g5pl0wiU8l3ZhHJZXgWdTV/LOVV2QHGv928qwZU+jhAWcfKPAGkCjC5OSOuBVkbefiEpeu2h74ziTwwnTa+0dYtH607WfLbooTh3Xs8YxnbtdPS/EOeG0R2TKjMG5DVnkCgYAT0Z+oiwJoNGv2/3k9bYDG7szAanbjxtzF2j7t4aoT/Uoj0iiblHrYy5lj6wsKHxTLZW8riJUdCyHuLWngeWqcU953YoFylm4FFK1rIbaQSGX/V8UsnwS4VBTT7uefdy9rWZSXHKIfkf/A74bd1ZHxKiu6ErPj7Lpc3g6v3nshJQKBgQC9FG8yyqEuc5Asljqp/b1MUvHVirkFC4mwdd3Es/q4OtUvI+mMuZQjVHVRFJlHEFr6/D1KLrO8clmIAV5/VQ+pIcynjt5ylsDG0wDFz6XKTEqPF4VLVpcO6M7Etic/hbvEjn8vLD+I+A2aAXvdfku37AO0am4b67Y6buSk/rqD8QKBgGmaVPqViroiqRnYf5zcuMiYh1UXecrIyZTXSg/DTscHAR6wMg5FH+DZdTb/1i9ECF6OEJowqjt12xzm3/JzU8rEWlmEpyuA5dB5kzMxqdKyhPTGbexqiyWEBP+bv3u1UTrGBrzvBkMNppkp+qP2YIEnjM2g6GpH05LqNOPY9hcp" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICmzCCAYMCBgGCtMPTnDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjIwODE5MDYyMDA4WhcNMzIwODE5MDYyMTQ4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsZ665kijSKEwfxptro8iJX3vD1ipmX6Baa5JFQhlpD4WHNJ7YApBvoUat3IQTvLeuPznHVF6nEyHIoxmKr29f8qAD9OxKGaMBbbO7CegnOgznHLOxaiW7YXyOs6j03ANUb4vNJqeR+i/lCxOAbD811va/ViuWk0p6R7u/AGV7eWP9cx2BxN6Xx9dJC12wek3PXUaUruDVaOSZu6hF2CTG2gK2qTAWUtFrgDOpCKnhypbZ6Pv+csdByhbhgx18ywWxZeOTrLuYrsPOvK1WirLZFMIqD8wWGjtDR4otIe2SK8EmcEgEAV/tF/RE0SjiQtTUcF6nWUfy/E9VGnXhxL6tAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJjTBsIwSryLhOj6e/bczs6IKAz8ZKRsB/NoV0GLHN2MTbP1MiThj3urXddNrAOaOp5DcYmh4rhLAbhe1AVy1OuQVL8MYXi0gT+vlkuoPQwtYbcAkadQ8EBo0Vb4rG3kyE5WgVKev2mdFlnRXMRJxjE066Y+tCKvA9tyI/z+QxOCKGoU/pEhXhDpFRU/AOSOAskv3rlL4t0rO8q7JdUAA39U0LBf2ptCvDlHl9oA0xZxS53VuZ990dXR8dTfuM4/IcA2BlAg5zBsmPp8rkz+X088SJ8XWFwRNuWNq6LVxb7atchGdJAOdv8enYWJJHUPd4B78BxgiUtVFWbnXaq98qU=" ], + "priority" : [ "100" ] + } + }, { + "id" : "576ffb1e-1a68-4b3c-b593-d48957e6032f", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAsfUHnfqhyPvT13cTkd96CnoX9QyxsLCw1cJ71b1SHkrJsVUHwIhgoUhriH0ZphM8MCMlWtia+icvDsuXSTaMqcQzv57rfaU5OXHOEQJnIzUgu/YObD92Scrb8xY0ZenWFpjIAF4+l6EF7EGt5XsmFuRrgjHYwkLxnHvE7sONJv9/lwufB9T1YWj59y+xTifrTFvLCOcgMjloWh/pkoe/HrIagSZ6QgIEKCw6T5oRJ/uMvNSKl2zelHfLsPaUu9fW9mp/Zh9ZqttbPHrKgM+X/YYpxjoIbTDt/wJ+BBsXue/39EDUZFRuhUg1oDlDE6HISsj69xbqMaBCUOthGxNpCwIDAQABAoIBAQCOSb4yacDMQ9apVDOYklgxYlgffmvhPBXYhO2hBARR5jiIitVs815X9uDMPEKy0HRyhNeMYN7hn0z4Bn9Lcctcvl0mHPpr9xzfOoiqfwJaW1N2FpKHqOJ6tDHTlvCEgEjGvphD+xcPBBgJRKa3cxUMfs0bR6HqCb7IAQqAYmlNQO2JzHwjP/yCMvcMgvcVFi/birhn5HkWlybMoxb3Jd4p9tgAjGSZvqHeJj74LnCYXvL771ZTgt8djnEctEVRWryuBMd+uAe/Ar0dba40HbnL6u3WPtXRj/YcR/OMMK1scmi2BqJwrTlcqB9KfOcbR8/JEwGw6FOqib4WoGGqfYO5AoGBAN0QX4tZmllJxzfiVc4GxYYbRBr46xItBUEtztupi7su/X+Q7eiSRJYSt10ESPElBtjb45KHkg/ffh4QPvPEjq2R8ROQMxkfn+TC3jCvK0kEddqzGXKtuP5i3mwwa4MkhWSSFUq6W5b11hWWEJvzwoPiadw2gpIYcfujzifszgMFAoGBAM4UrRLlNCrFBHOrHIRUrl0S1Ft8/QCsUAl4jIp7WNdlnpn+Wj5SQHLaYHPf+QrVkVxzN+xd9Ol3kqWOcGtFtz1Kuxpb/YtPVHm2l6JazgP+VN3wDTvKgrW7OzZuulhHEyaRmwt3s/AorxIgwfpxs/oy8OCUv1840Q89YLf6JJjPAoGARQw2K5pjIXxv4z6oul9XFtoxXZNeKSEywPcD44yDfoXg3BVymfAFyDI0X7NU7S05hEa6QCxkLN0L0WwVnaJJRmGNQSULMM+164gKSn6MMJRaE2NZkX49iAdtnbFKA35Gw/D1AZBPx0kmAzwKGAv9N6BinEvSYLuN7qFtZP8MIdkCgYEAohuvnbqBE9fRTa/fidUXKA3k0Gb8mmfxudGDNHgdBathXJ+xm26WVgKkduJLhJNFemUEK8IpIvI1gFgQ5MF9iBBeKDkOtGRd/jR9CXDGuGt7lO39avg9Y/l5dbMakNCwJtnJDfdGq3dFaEwuavTAb+Ncij1YYO5Pvd45U9/IpA8CgYBNQUhzKpE1TG+q75UAs3O6BvN4fldxWMjgMlkApfmnPWUuNPhioMyUeHUt/GllE0mm7F+QU7MSWXZO6VHw5nkCuOObfib+OPmau7C4SsHCd8czd+T+9kyLJK0ZC6s5+IgFCImVqkySM9UuptmPWsdz9NbzGtYkcuwJrVHxoevtOQ==" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICmzCCAYMCBgGCtMPUHDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjIwODE5MDYyMDA4WhcNMzIwODE5MDYyMTQ4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx9Qed+qHI+9PXdxOR33oKehf1DLGwsLDVwnvVvVIeSsmxVQfAiGChSGuIfRmmEzwwIyVa2Jr6Jy8Oy5dJNoypxDO/nut9pTk5cc4RAmcjNSC79g5sP3ZJytvzFjRl6dYWmMgAXj6XoQXsQa3leyYW5GuCMdjCQvGce8Tuw40m/3+XC58H1PVhaPn3L7FOJ+tMW8sI5yAyOWhaH+mSh78eshqBJnpCAgQoLDpPmhEn+4y81IqXbN6Ud8uw9pS719b2an9mH1mq21s8esqAz5f9hinGOghtMO3/An4EGxe57/f0QNRkVG6FSDWgOUMTochKyPr3FuoxoEJQ62EbE2kLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAfhCLcbGQIeOeRono42Bx/dp7IlVZbCFcdwB83awD/W8ACaRmnwQYL+3XyJlCOLjLGUvQnvPONp9yEK09dZxQvILv5WNeJ2IyH7uNMB6o7POBMnxFVkVTa03T13lQur9t/MmyF6XD9uMSEsb22F5/CAham1FZ02P/E7bnnkubFoNR7z6aWC/D0wTxDXzUe6DdTdthJpJpw+JQkq0tb+GW0MP3vxIjlEpbIye6ETlGtn5zjlSF1Nswv84/MSI367nb1982UVJWvsHGjyF2iVcei24DR99RM632llV7y5rzCRBleyZmRXriEDF114JrLcWwvVTUyRTdEzKHrZx0RP5Ak=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "bc87ad24-becc-4c81-b062-164fe7206668", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "e94955c6-2815-4d38-b90d-dc245115d6cb" ], + "secret" : [ "2ZU_Dx4DTO6wcqbYKMCWBw" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "88de600a-fb85-46a3-8729-7c81827943ee", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "a63f756d-163f-46b1-a7b8-102f7daac662", + "alias" : "Authentication Options", + "description" : "Authentication options.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "basic-auth", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "basic-auth-otp", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "c3b7632e-0417-447a-a20d-c0c7880ed4b5", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "35890077-9430-4bbd-8a67-bcd4f855a33b", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "2fb70949-779b-4881-9f62-f89f3ff1ca36", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "c23477cd-6e3f-444f-b9d9-b355d92163ef", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "1336e226-e4ea-4b02-b922-751bc5fb7965", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "99bf6afa-2b89-453f-bb15-5f837b4c2ab1", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "a3cabd98-262d-43d7-bba4-df6476444b40", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "cabd2c42-5145-452a-ab08-d3c87a40afa3", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "7749f55c-3218-4ce7-9d52-0411de70dab1", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "34462526-355a-406a-9cc7-056f69f4ed13", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "89c54236-bee8-453b-ac0d-d5d5b293b301", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "4f5178b5-8b7c-4ba2-8fe1-46856d50395a", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "2c53dfdb-dffb-4d23-8d71-eb8588528ede", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "5fc8c202-b566-497f-9290-8ec8fceef0a5", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Authentication Options", + "userSetupAllowed" : false + } ] + }, { + "id" : "bc8a31cd-ca15-4eaf-89b0-27e85cd2bf95", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "aa4c56c3-9b58-4bce-afd2-0903488919bd", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-profile-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a8bec154-7966-4de2-a8ff-447182f7d971", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "70bb30f8-b553-4a17-aa33-4195e9c93904", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "2f0a7833-c6dc-4c22-976a-8a8c896a09cf", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "1243d693-d577-4b7f-b544-ad9e4b369faf", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "parRequestUriLifespan" : "60", + "cibaInterval" : "5" + }, + "keycloakVersion" : "18.0.2", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} ] diff --git a/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 0000000000000000000000000000000000000000..79b126e6cdb86bec1f4f08c205de8961bde1934a --- /dev/null +++ b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.mockito.junit.jupiter.MockitoExtension \ No newline at end of file