Updating the UP configuration needs to trigger an admin event
Close #23896 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
bee7595275
commit
d841971ff4
14 changed files with 327 additions and 17 deletions
|
@ -21,6 +21,7 @@ package org.keycloak.representations.userprofile.config;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration of the Attribute.
|
* Configuration of the Attribute.
|
||||||
|
@ -170,4 +171,28 @@ public class UPAttribute implements Cloneable {
|
||||||
attr.setGroup(this.group);
|
attr.setGroup(this.group);
|
||||||
return attr;
|
return attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final UPAttribute other = (UPAttribute) obj;
|
||||||
|
return Objects.equals(this.name, other.name)
|
||||||
|
&& Objects.equals(this.displayName, other.displayName)
|
||||||
|
&& Objects.equals(this.group, other.group)
|
||||||
|
&& Objects.equals(this.validations, other.validations)
|
||||||
|
&& Objects.equals(this.annotations, other.annotations)
|
||||||
|
&& Objects.equals(this.required, other.required)
|
||||||
|
&& Objects.equals(this.permissions, other.permissions)
|
||||||
|
&& Objects.equals(this.selector, other.selector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.representations.userprofile.config;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
@ -75,4 +76,22 @@ public class UPAttributePermissions implements Cloneable {
|
||||||
Set<String> edit = this.edit == null ? null : new HashSet<>(this.edit);
|
Set<String> edit = this.edit == null ? null : new HashSet<>(this.edit);
|
||||||
return new UPAttributePermissions(view, edit);
|
return new UPAttributePermissions(view, edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(view, edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final UPAttributePermissions other = (UPAttributePermissions) obj;
|
||||||
|
return Objects.equals(this.view, other.view)
|
||||||
|
&& Objects.equals(this.edit, other.edit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.keycloak.representations.userprofile.config;
|
package org.keycloak.representations.userprofile.config;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
@ -82,4 +83,21 @@ public class UPAttributeRequired implements Cloneable {
|
||||||
return new UPAttributeRequired(roles, scopes);
|
return new UPAttributeRequired(roles, scopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(roles, scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final UPAttributeRequired other = (UPAttributeRequired) obj;
|
||||||
|
return Objects.equals(this.roles, other.roles)
|
||||||
|
&& Objects.equals(this.scopes, other.scopes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.keycloak.representations.userprofile.config;
|
package org.keycloak.representations.userprofile.config;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,4 +57,21 @@ public class UPAttributeSelector implements Cloneable {
|
||||||
protected UPAttributeSelector clone() {
|
protected UPAttributeSelector clone() {
|
||||||
return new UPAttributeSelector(scopes == null ? null : new HashSet<>(scopes));
|
return new UPAttributeSelector(scopes == null ? null : new HashSet<>(scopes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final UPAttributeSelector other = (UPAttributeSelector) obj;
|
||||||
|
return Objects.equals(this.scopes, other.scopes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.keycloak.representations.userprofile.config;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
@ -137,4 +138,23 @@ public class UPConfig implements Cloneable {
|
||||||
|
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(attributes, groups, unmanagedAttributePolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final UPConfig other = (UPConfig) obj;
|
||||||
|
return Objects.equals(this.attributes, other.attributes)
|
||||||
|
&& Objects.equals(this.groups, other.groups)
|
||||||
|
&& this.unmanagedAttributePolicy == other.unmanagedAttributePolicy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.keycloak.representations.userprofile.config;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration of the attribute group.
|
* Configuration of the attribute group.
|
||||||
|
@ -82,4 +83,24 @@ public class UPGroup implements Cloneable {
|
||||||
group.setAnnotations(this.annotations == null ? null : new HashMap<>(this.annotations));
|
group.setAnnotations(this.annotations == null ? null : new HashMap<>(this.annotations));
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final UPGroup other = (UPGroup) obj;
|
||||||
|
return Objects.equals(this.name, other.name)
|
||||||
|
&& Objects.equals(this.displayHeader, other.displayHeader)
|
||||||
|
&& Objects.equals(this.displayDescription, other.displayDescription)
|
||||||
|
&& Objects.equals(this.annotations, other.annotations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
|
|
||||||
package org.keycloak.admin.ui.rest;
|
package org.keycloak.admin.ui.rest;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
import jakarta.ws.rs.InternalServerErrorException;
|
import jakarta.ws.rs.InternalServerErrorException;
|
||||||
import jakarta.ws.rs.PUT;
|
import jakarta.ws.rs.PUT;
|
||||||
|
@ -35,7 +33,6 @@ import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||||
import org.keycloak.services.resources.admin.RealmAdminResource;
|
import org.keycloak.services.resources.admin.RealmAdminResource;
|
||||||
import org.keycloak.services.resources.admin.UserProfileResource;
|
import org.keycloak.services.resources.admin.UserProfileResource;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This JAX-RS resource is decorating the Admin Realm API in order to support specific behaviors from the
|
* This JAX-RS resource is decorating the Admin Realm API in order to support specific behaviors from the
|
||||||
|
@ -48,10 +45,12 @@ public class UIRealmResource {
|
||||||
private final RealmAdminResource delegate;
|
private final RealmAdminResource delegate;
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final AdminPermissionEvaluator auth;
|
private final AdminPermissionEvaluator auth;
|
||||||
|
private final AdminEventBuilder adminEvent;
|
||||||
|
|
||||||
public UIRealmResource(KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
|
public UIRealmResource(KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
|
this.adminEvent = adminEvent;
|
||||||
this.delegate = new RealmAdminResource(session, auth, adminEvent);
|
this.delegate = new RealmAdminResource(session, auth, adminEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +74,9 @@ public class UIRealmResource {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response response = new UserProfileResource(session, auth).update(upConfig);
|
UserProfileResource userProfileResource = new UserProfileResource(session, auth, adminEvent);
|
||||||
|
if (!upConfig.equals(userProfileResource.getConfiguration())) {
|
||||||
|
Response response = userProfileResource.update(upConfig);
|
||||||
|
|
||||||
if (isSuccessful(response)) {
|
if (isSuccessful(response)) {
|
||||||
return;
|
return;
|
||||||
|
@ -83,6 +84,7 @@ public class UIRealmResource {
|
||||||
|
|
||||||
throw new InternalServerErrorException("Failed to update user profile configuration");
|
throw new InternalServerErrorException("Failed to update user profile configuration");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isSuccessful(Response response) {
|
private boolean isSuccessful(Response response) {
|
||||||
return Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily());
|
return Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily());
|
||||||
|
|
|
@ -186,5 +186,10 @@ public enum ResourceType {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
, CUSTOM;
|
, CUSTOM
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user profile configuration
|
||||||
|
*/
|
||||||
|
, USER_PROFILE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||||
|
|
||||||
import org.keycloak.component.ComponentValidationException;
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.idm.UserProfileMetadata;
|
import org.keycloak.representations.idm.UserProfileMetadata;
|
||||||
|
@ -54,14 +56,15 @@ import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
public class UserProfileResource {
|
public class UserProfileResource {
|
||||||
|
|
||||||
protected final KeycloakSession session;
|
protected final KeycloakSession session;
|
||||||
|
protected final AdminEventBuilder adminEvent;
|
||||||
protected final RealmModel realm;
|
protected final RealmModel realm;
|
||||||
private final AdminPermissionEvaluator auth;
|
private final AdminPermissionEvaluator auth;
|
||||||
|
|
||||||
public UserProfileResource(KeycloakSession session, AdminPermissionEvaluator auth) {
|
public UserProfileResource(KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.realm = session.getContext().getRealm();
|
this.realm = session.getContext().getRealm();
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
|
this.adminEvent = adminEvent.resource(ResourceType.USER_PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -101,6 +104,11 @@ public class UserProfileResource {
|
||||||
throw ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST);
|
throw ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adminEvent.operation(OperationType.UPDATE)
|
||||||
|
.resourcePath(session.getContext().getUri())
|
||||||
|
.representation(config)
|
||||||
|
.success();
|
||||||
|
|
||||||
return Response.ok(t.getConfiguration()).type(MediaType.APPLICATION_JSON).build();
|
return Response.ok(t.getConfiguration()).type(MediaType.APPLICATION_JSON).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@ import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.keycloak.models.Constants.SESSION_NOTE_LIGHTWEIGHT_USER;
|
|
||||||
import static org.keycloak.models.utils.KeycloakModelUtils.findGroupByPath;
|
import static org.keycloak.models.utils.KeycloakModelUtils.findGroupByPath;
|
||||||
import static org.keycloak.userprofile.UserProfileContext.USER_API;
|
import static org.keycloak.userprofile.UserProfileContext.USER_API;
|
||||||
|
|
||||||
|
@ -460,7 +459,7 @@ public class UsersResource {
|
||||||
*/
|
*/
|
||||||
@Path("profile")
|
@Path("profile")
|
||||||
public UserProfileResource userProfile() {
|
public UserProfileResource userProfile() {
|
||||||
return new UserProfileResource(session, auth);
|
return new UserProfileResource(session, auth, adminEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
|
private Stream<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
|
||||||
|
|
|
@ -51,13 +51,13 @@ public class UserTestWithUserProfile extends UserTest {
|
||||||
public void onBefore() throws IOException {
|
public void onBefore() throws IOException {
|
||||||
RealmRepresentation realmRep = realm.toRepresentation();
|
RealmRepresentation realmRep = realm.toRepresentation();
|
||||||
VerifyProfileTest.disableDynamicUserProfile(realm);
|
VerifyProfileTest.disableDynamicUserProfile(realm);
|
||||||
assertAdminEvents.poll();
|
assertAdminEvents.poll(); // update realm
|
||||||
realm.update(realmRep);
|
assertAdminEvents.poll(); // set UP configuration
|
||||||
assertAdminEvents.poll();
|
|
||||||
VerifyProfileTest.enableDynamicUserProfile(realmRep);
|
VerifyProfileTest.enableDynamicUserProfile(realmRep);
|
||||||
realm.update(realmRep);
|
realm.update(realmRep);
|
||||||
assertAdminEvents.poll();
|
assertAdminEvents.poll();
|
||||||
VerifyProfileTest.setUserProfileConfiguration(realm, null);
|
VerifyProfileTest.setUserProfileConfiguration(realm, null);
|
||||||
|
assertAdminEvents.poll();
|
||||||
UPConfig upConfig = realm.users().userProfile().getConfiguration();
|
UPConfig upConfig = realm.users().userProfile().getConfiguration();
|
||||||
|
|
||||||
for (String name : managedAttributes) {
|
for (String name : managedAttributes) {
|
||||||
|
@ -65,6 +65,7 @@ public class UserTestWithUserProfile extends UserTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
VerifyProfileTest.setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig));
|
VerifyProfileTest.setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig));
|
||||||
|
assertAdminEvents.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -44,7 +44,10 @@ import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
|
@ -61,6 +64,8 @@ import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.VerifyProfilePage;
|
import org.keycloak.testsuite.pages.VerifyProfilePage;
|
||||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||||
|
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||||
|
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||||
import org.keycloak.testsuite.util.ClientScopeBuilder;
|
import org.keycloak.testsuite.util.ClientScopeBuilder;
|
||||||
import org.keycloak.testsuite.util.JsonTestUtils;
|
import org.keycloak.testsuite.util.JsonTestUtils;
|
||||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||||
|
@ -162,6 +167,9 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
|
||||||
@Rule
|
@Rule
|
||||||
public AssertEvents events = new AssertEvents(this);
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this);
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
protected AppPage appPage;
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@ -1193,7 +1201,14 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UPConfig setUserProfileConfiguration(String configuration) {
|
protected UPConfig setUserProfileConfiguration(String configuration) {
|
||||||
return setUserProfileConfiguration(testRealm(), configuration);
|
assertAdminEvents.clear();
|
||||||
|
UPConfig result = setUserProfileConfiguration(testRealm(), configuration);
|
||||||
|
AdminEventRepresentation adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME,
|
||||||
|
OperationType.UPDATE, AdminEventPaths.userProfilePath(), ResourceType.USER_PROFILE);
|
||||||
|
Assert.assertTrue("Incorrect representation in event", StringUtils.isBlank(configuration)
|
||||||
|
? StringUtils.isBlank(adminEvent.getRepresentation())
|
||||||
|
: StringUtils.isNotBlank(adminEvent.getRepresentation()));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enableDynamicUserProfile(RealmRepresentation testRealm) {
|
public static void enableDynamicUserProfile(RealmRepresentation testRealm) {
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* * Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||||
|
* * and other contributors as indicated by the @author tags.
|
||||||
|
* *
|
||||||
|
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* * you may not use this file except in compliance with the License.
|
||||||
|
* * You may obtain a copy of the License at
|
||||||
|
* *
|
||||||
|
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* *
|
||||||
|
* * Unless required by applicable law or agreed to in writing, software
|
||||||
|
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* * See the License for the specific language governing permissions and
|
||||||
|
* * limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.keycloak.testsuite.user.profile;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.client.Client;
|
||||||
|
import jakarta.ws.rs.client.Entity;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.resource.BearerAuthFilter;
|
||||||
|
import org.keycloak.admin.ui.rest.model.UIRealmRepresentation;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPAttributeRequired;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||||
|
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
|
||||||
|
import org.keycloak.userprofile.config.UPConfigUtils;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
|
||||||
|
public class UIRealmResourceTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
if (testRealm.getAttributes() == null) {
|
||||||
|
testRealm.setAttributes(new HashMap<>());
|
||||||
|
}
|
||||||
|
testRealm.getAttributes().put(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoUpdateUserProfile() throws IOException {
|
||||||
|
RealmRepresentation rep = testRealm().toRepresentation();
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, null));
|
||||||
|
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
assertAdminEvents.assertEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSameUpdateUserProfile() throws IOException {
|
||||||
|
RealmRepresentation rep = testRealm().toRepresentation();
|
||||||
|
UPConfig upConfig = testRealm().users().userProfile().getConfiguration();
|
||||||
|
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
assertAdminEvents.assertEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateUserProfileModification() throws IOException {
|
||||||
|
RealmRepresentation rep = testRealm().toRepresentation();
|
||||||
|
UPConfig upConfig = testRealm().users().userProfile().getConfiguration();
|
||||||
|
upConfig.addOrReplaceAttribute(new UPAttribute("foo",
|
||||||
|
new UPAttributePermissions(Set.of(), Set.of(UPConfigUtils.ROLE_USER, UPConfigUtils.ROLE_ADMIN))));
|
||||||
|
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
AdminEventRepresentation adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
Assert.assertNotNull(adminEvent.getRepresentation());
|
||||||
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
|
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
|
upConfig.getAttribute("foo").setDisplayName("Foo");
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
|
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
|
upConfig.getAttribute("foo").setPermissions(new UPAttributePermissions(Set.of(), Set.of(UPConfigUtils.ROLE_USER)));
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
|
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
|
upConfig.getAttribute("foo").setRequired(new UPAttributeRequired(Set.of(UPConfigUtils.ROLE_ADMIN, UPConfigUtils.ROLE_USER), Set.of()));
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
|
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
|
upConfig.getAttribute("foo").setValidations(Map.of("length", Map.of("min", "3", "max", "128")));
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
|
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
|
assertAdminEvents.assertEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRealmExt(UIRealmRepresentation rep) {
|
||||||
|
try (Client client = Keycloak.getClientProvider().newRestEasyClient(null, null, true)) {
|
||||||
|
Response response = client.target(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth")
|
||||||
|
.path("/admin/realms/" + rep.getRealm() + "/ui-ext")
|
||||||
|
.register(new BearerAuthFilter(adminClient.tokenManager()))
|
||||||
|
.request(MediaType.APPLICATION_JSON)
|
||||||
|
.put(Entity.entity(rep, MediaType.APPLICATION_JSON));
|
||||||
|
Assert.assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UIRealmRepresentation toUIRealmRepresentation(RealmRepresentation realm, UPConfig upConfig) throws IOException {
|
||||||
|
UIRealmRepresentation uiRealm = JsonSerialization.readValue(JsonSerialization.writeValueAsString(realm), UIRealmRepresentation.class);
|
||||||
|
uiRealm.setUpConfig(upConfig);
|
||||||
|
return uiRealm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UPConfig toUpConfig(String representation) throws IOException {
|
||||||
|
return JsonSerialization.readValue(representation, UPConfig.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ import org.keycloak.admin.client.resource.RoleByIdResource;
|
||||||
import org.keycloak.admin.client.resource.RoleMappingResource;
|
import org.keycloak.admin.client.resource.RoleMappingResource;
|
||||||
import org.keycloak.admin.client.resource.RoleResource;
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
import org.keycloak.admin.client.resource.RolesResource;
|
import org.keycloak.admin.client.resource.RolesResource;
|
||||||
|
import org.keycloak.admin.client.resource.UserProfileResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.admin.client.resource.UsersResource;
|
import org.keycloak.admin.client.resource.UsersResource;
|
||||||
|
|
||||||
|
@ -69,6 +70,12 @@ public class AdminEventPaths {
|
||||||
return uri.toString();
|
return uri.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String userProfilePath() {
|
||||||
|
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "users")
|
||||||
|
.path(UsersResource.class, "userProfile")
|
||||||
|
.build();
|
||||||
|
return uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// CLIENT RESOURCE
|
// CLIENT RESOURCE
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue