Bugfix: "User Profile" attributes not available for Users Attribute search, when admin user does not have view- or manage-realm realm-management role (#31771)
- UIRealmResource: add "info" sub-resource to get realm-related information, which is visible for ALL admins (users having any realm-management role); for now, only provide the information whether any user profile provider is enabled - UIRealmResourceTest: test the new endpoint, including permissions check - UserDataTable.tsx: use this resource to get the info whether user profile providers are enabled, instead of using the realm components resource (which requires "view-realm" permissions) - .../cypress/e2e/users_attribute_search_test.spec.ts: add cypress test to test the attribute search with minimum access rights - further small changes for reuse of components, test-code etc Closes #27536 Signed-off-by: Daniel Fesenmeyer <daniel.fesenmeyer@bosch.com>
This commit is contained in:
parent
c532751ff4
commit
87da4011f7
18 changed files with 538 additions and 87 deletions
|
@ -86,7 +86,7 @@ describe("Client Scopes test", () => {
|
||||||
|
|
||||||
it("should filter items by Assigned type All types", () => {
|
it("should filter items by Assigned type All types", () => {
|
||||||
listingPage
|
listingPage
|
||||||
.selectFilter(Filter.AssignedType)
|
.selectClientScopeFilter(Filter.AssignedType)
|
||||||
.selectSecondaryFilterAssignedType(FilterAssignedType.AllTypes)
|
.selectSecondaryFilterAssignedType(FilterAssignedType.AllTypes)
|
||||||
.itemExist(FilterAssignedType.Default, true)
|
.itemExist(FilterAssignedType.Default, true)
|
||||||
.itemExist(FilterAssignedType.Optional, true)
|
.itemExist(FilterAssignedType.Optional, true)
|
||||||
|
@ -95,7 +95,7 @@ describe("Client Scopes test", () => {
|
||||||
|
|
||||||
it("should filter items by Assigned type Default", () => {
|
it("should filter items by Assigned type Default", () => {
|
||||||
listingPage
|
listingPage
|
||||||
.selectFilter(Filter.AssignedType)
|
.selectClientScopeFilter(Filter.AssignedType)
|
||||||
.selectSecondaryFilterAssignedType(FilterAssignedType.Default)
|
.selectSecondaryFilterAssignedType(FilterAssignedType.Default)
|
||||||
.itemExist(FilterAssignedType.Default, true)
|
.itemExist(FilterAssignedType.Default, true)
|
||||||
.itemExist(FilterAssignedType.Optional, false)
|
.itemExist(FilterAssignedType.Optional, false)
|
||||||
|
@ -104,7 +104,7 @@ describe("Client Scopes test", () => {
|
||||||
|
|
||||||
it("should filter items by Assigned type Optional", () => {
|
it("should filter items by Assigned type Optional", () => {
|
||||||
listingPage
|
listingPage
|
||||||
.selectFilter(Filter.AssignedType)
|
.selectClientScopeFilter(Filter.AssignedType)
|
||||||
.selectSecondaryFilterAssignedType(FilterAssignedType.Optional)
|
.selectSecondaryFilterAssignedType(FilterAssignedType.Optional)
|
||||||
.itemExist(FilterAssignedType.Default, false)
|
.itemExist(FilterAssignedType.Default, false)
|
||||||
.itemExist(FilterAssignedType.Optional, true)
|
.itemExist(FilterAssignedType.Optional, true)
|
||||||
|
@ -113,7 +113,7 @@ describe("Client Scopes test", () => {
|
||||||
|
|
||||||
it("should filter items by Protocol All", () => {
|
it("should filter items by Protocol All", () => {
|
||||||
listingPage
|
listingPage
|
||||||
.selectFilter(Filter.Protocol)
|
.selectClientScopeFilter(Filter.Protocol)
|
||||||
.selectSecondaryFilterProtocol(FilterProtocol.All);
|
.selectSecondaryFilterProtocol(FilterProtocol.All);
|
||||||
sidebarPage.waitForPageLoad();
|
sidebarPage.waitForPageLoad();
|
||||||
listingPage
|
listingPage
|
||||||
|
@ -124,7 +124,7 @@ describe("Client Scopes test", () => {
|
||||||
|
|
||||||
it("should filter items by Protocol SAML", () => {
|
it("should filter items by Protocol SAML", () => {
|
||||||
listingPage
|
listingPage
|
||||||
.selectFilter(Filter.Protocol)
|
.selectClientScopeFilter(Filter.Protocol)
|
||||||
.selectSecondaryFilterProtocol(FilterProtocol.SAML)
|
.selectSecondaryFilterProtocol(FilterProtocol.SAML)
|
||||||
.itemExist(FilterProtocol.SAML, true)
|
.itemExist(FilterProtocol.SAML, true)
|
||||||
.itemExist(openIDConnectItemText, false); //using FilterProtocol.OpenID will fail, text does not match.
|
.itemExist(openIDConnectItemText, false); //using FilterProtocol.OpenID will fail, text does not match.
|
||||||
|
@ -132,7 +132,7 @@ describe("Client Scopes test", () => {
|
||||||
|
|
||||||
it("should filter items by Protocol OpenID", () => {
|
it("should filter items by Protocol OpenID", () => {
|
||||||
listingPage
|
listingPage
|
||||||
.selectFilter(Filter.Protocol)
|
.selectClientScopeFilter(Filter.Protocol)
|
||||||
.selectSecondaryFilterProtocol(FilterProtocol.OpenID)
|
.selectSecondaryFilterProtocol(FilterProtocol.OpenID)
|
||||||
.itemExist(FilterProtocol.SAML, false)
|
.itemExist(FilterProtocol.SAML, false)
|
||||||
.itemExist(openIDConnectItemText, true); //using FilterProtocol.OpenID will fail, text does not match.
|
.itemExist(openIDConnectItemText, true); //using FilterProtocol.OpenID will fail, text does not match.
|
||||||
|
|
100
js/apps/admin-ui/cypress/e2e/users_attribute_search_test.spec.ts
Normal file
100
js/apps/admin-ui/cypress/e2e/users_attribute_search_test.spec.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import SidebarPage from "../support/pages/admin-ui/SidebarPage";
|
||||||
|
import LoginPage from "../support/pages/LoginPage";
|
||||||
|
import { keycloakBefore } from "../support/util/keycloak_hooks";
|
||||||
|
import adminClient from "../support/util/AdminClient";
|
||||||
|
import {
|
||||||
|
DefaultUserAttribute,
|
||||||
|
UserFilterType,
|
||||||
|
} from "../support/pages/admin-ui/manage/users/UsersListingPage";
|
||||||
|
import UsersPage from "../support/pages/admin-ui/manage/users/UsersPage";
|
||||||
|
|
||||||
|
describe("Query by user attributes", () => {
|
||||||
|
const loginPage = new LoginPage();
|
||||||
|
const sidebarPage = new SidebarPage();
|
||||||
|
const usersPage = new UsersPage();
|
||||||
|
const listingPage = usersPage.listing();
|
||||||
|
|
||||||
|
const emailSuffix = "@example.org";
|
||||||
|
|
||||||
|
const user1Username = "user-attrs-1";
|
||||||
|
const user1FirstName = "John";
|
||||||
|
const user1LastName = "Doe";
|
||||||
|
const user1Pwd = "pwd";
|
||||||
|
const user2Username = "user-attrs-2";
|
||||||
|
const user2FirstName = "Jane";
|
||||||
|
const user2LastName = user1LastName;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await cleanupTestData();
|
||||||
|
const user1 = await adminClient.createUser({
|
||||||
|
username: user1Username,
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
type: "password",
|
||||||
|
value: user1Pwd,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
email: user1Username + emailSuffix,
|
||||||
|
firstName: user1FirstName,
|
||||||
|
lastName: user1LastName,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
const user1Id = user1.id!;
|
||||||
|
await adminClient.addClientRoleToUser(user1Id, "master-realm", [
|
||||||
|
"view-users",
|
||||||
|
]);
|
||||||
|
|
||||||
|
await adminClient.createUser({
|
||||||
|
username: user2Username,
|
||||||
|
email: user2Username + emailSuffix,
|
||||||
|
firstName: user2FirstName,
|
||||||
|
lastName: user2LastName,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
loginPage.logIn(user1Username, user1Pwd);
|
||||||
|
keycloakBefore();
|
||||||
|
sidebarPage.goToUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await cleanupTestData();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function cleanupTestData() {
|
||||||
|
await adminClient.deleteUser(user1Username, true);
|
||||||
|
await adminClient.deleteUser(user2Username, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("Query with one attribute condition", () => {
|
||||||
|
listingPage
|
||||||
|
.selectUserSearchFilter(UserFilterType.AttributeSearch)
|
||||||
|
.openUserAttributesSearchForm()
|
||||||
|
.addUserAttributeSearchCriteria(
|
||||||
|
DefaultUserAttribute.lastName,
|
||||||
|
user1LastName,
|
||||||
|
)
|
||||||
|
.triggerAttributesSearch()
|
||||||
|
.itemExist(user1Username, true)
|
||||||
|
.itemExist(user2Username, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Query with two attribute conditions", () => {
|
||||||
|
listingPage
|
||||||
|
.selectUserSearchFilter(UserFilterType.AttributeSearch)
|
||||||
|
.openUserAttributesSearchForm()
|
||||||
|
.addUserAttributeSearchCriteria(
|
||||||
|
DefaultUserAttribute.lastName,
|
||||||
|
user1LastName,
|
||||||
|
)
|
||||||
|
.addUserAttributeSearchCriteria(
|
||||||
|
DefaultUserAttribute.firstName,
|
||||||
|
user1FirstName,
|
||||||
|
)
|
||||||
|
.triggerAttributesSearch()
|
||||||
|
.itemExist(user1Username, true)
|
||||||
|
.itemExist(user2Username, false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -52,7 +52,6 @@ export default class ListingPage extends CommonElements {
|
||||||
public tableRowItem = "tbody tr[data-ouia-component-type]:visible";
|
public tableRowItem = "tbody tr[data-ouia-component-type]:visible";
|
||||||
#table = "table[aria-label]";
|
#table = "table[aria-label]";
|
||||||
#filterSessionDropdownButton = ".pf-v5-c-select button:nth-child(1)";
|
#filterSessionDropdownButton = ".pf-v5-c-select button:nth-child(1)";
|
||||||
#searchTypeButton = "[data-testid='clientScopeSearch']";
|
|
||||||
#filterDropdownButton = "[data-testid='clientScopeSearchType']";
|
#filterDropdownButton = "[data-testid='clientScopeSearchType']";
|
||||||
#protocolFilterDropdownButton = "[data-testid='clientScopeSearchProtocol']";
|
#protocolFilterDropdownButton = "[data-testid='clientScopeSearchProtocol']";
|
||||||
#kebabMenu = "[data-testid='kebab']";
|
#kebabMenu = "[data-testid='kebab']";
|
||||||
|
@ -320,9 +319,13 @@ export default class ListingPage extends CommonElements {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectFilter(filter: Filter) {
|
selectClientScopeFilter(filter: Filter) {
|
||||||
cy.get(this.#searchTypeButton).click();
|
return this.selectFilter("clientScopeSearch", filter);
|
||||||
cy.get(this.#dropdownItem).contains(filter).click();
|
}
|
||||||
|
|
||||||
|
protected selectFilter(searchTypeButtonTestId: string, filterStr: string) {
|
||||||
|
cy.get(`[data-testid='${searchTypeButtonTestId}']`).click();
|
||||||
|
cy.get(this.#dropdownItem).contains(filterStr).click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,14 @@ export default class PageObject {
|
||||||
#selectMenuToggleBtn = ".pf-v5-c-menu-toggle";
|
#selectMenuToggleBtn = ".pf-v5-c-menu-toggle";
|
||||||
#switchInput = ".pf-v5-c-switch__input";
|
#switchInput = ".pf-v5-c-switch__input";
|
||||||
#formLabel = ".pf-v5-c-form__label";
|
#formLabel = ".pf-v5-c-form__label";
|
||||||
#chipGroup = ".pf-v5-c-chip-group";
|
|
||||||
#chipGroupCloseBtn = ".pf-v5-c-chip-group__close";
|
#chipGroupCloseBtn = ".pf-v5-c-chip-group__close";
|
||||||
#chipItem = ".pf-v5-c-chip-group__list-item";
|
#chipItem = ".pf-v5-c-chip-group__list-item";
|
||||||
#emptyStateDiv = ".pf-v5-c-empty-state:visible";
|
#emptyStateDiv = ".pf-v5-c-empty-state:visible";
|
||||||
#toolbarActionsButton = ".pf-v5-c-toolbar button[aria-label='Actions']";
|
#toolbarActionsButton = ".pf-v5-c-toolbar button[aria-label='Actions']";
|
||||||
#breadcrumbItem = ".pf-v5-c-breadcrumb .pf-v5-c-breadcrumb__item";
|
#breadcrumbItem = ".pf-v5-c-breadcrumb .pf-v5-c-breadcrumb__item";
|
||||||
|
|
||||||
|
genericChipGroupSelector = ".pf-v5-c-chip-group";
|
||||||
|
|
||||||
protected assertExist(element: Cypress.Chainable<JQuery>, exist: boolean) {
|
protected assertExist(element: Cypress.Chainable<JQuery>, exist: boolean) {
|
||||||
element.should((!exist ? "not." : "") + "exist");
|
element.should((!exist ? "not." : "") + "exist");
|
||||||
return this;
|
return this;
|
||||||
|
@ -281,41 +282,55 @@ export default class PageObject {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#getChipGroup(groupName: string) {
|
#getChipGroup(groupSelector: string, groupName: string) {
|
||||||
return cy.get(this.#chipGroup).contains(groupName).parent().parent();
|
return cy.get(groupSelector).contains(groupName).parent().parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
#getChipItem(itemName: string) {
|
#getChipGroupWithLabel(groupSelector: string, label: string) {
|
||||||
return cy.get(this.#chipItem).contains(itemName).parent();
|
cy.get(groupSelector)
|
||||||
|
.parent()
|
||||||
|
.find(".pf-v5-c-chip-group__label")
|
||||||
|
.contains(label);
|
||||||
|
|
||||||
|
return cy.get(groupSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
#getChipGroupItem(groupName: string, itemName: string) {
|
#getChipGroupItem(
|
||||||
return this.#getChipGroup(groupName)
|
groupSelector: string,
|
||||||
|
groupName: string,
|
||||||
|
itemName: string,
|
||||||
|
) {
|
||||||
|
return this.#getChipGroup(groupSelector, groupName)
|
||||||
.find(this.#chipItem)
|
.find(this.#chipItem)
|
||||||
.contains(itemName)
|
.contains(itemName)
|
||||||
.parent();
|
.parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeChipGroup(groupName: string) {
|
protected removeChipGroup(groupSelector: string, groupName: string) {
|
||||||
this.#getChipGroup(groupName)
|
this.#getChipGroup(groupSelector, groupName)
|
||||||
.find(this.#chipGroupCloseBtn)
|
.find(this.#chipGroupCloseBtn)
|
||||||
.find("button")
|
.find("button")
|
||||||
.click();
|
.click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeChipItem(itemName: string) {
|
protected removeChipGroupItem(
|
||||||
this.#getChipItem(itemName).find("button").click();
|
groupSelector: string,
|
||||||
|
groupName: string,
|
||||||
|
itemName: string,
|
||||||
|
) {
|
||||||
|
this.#getChipGroupItem(groupSelector, groupName, itemName)
|
||||||
|
.find("button")
|
||||||
|
.click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeChipGroupItem(groupName: string, itemName: string) {
|
protected assertChipGroupExist(
|
||||||
this.#getChipGroupItem(groupName, itemName).find("button").click();
|
groupSelector: string,
|
||||||
return this;
|
groupName: string,
|
||||||
}
|
exist: boolean,
|
||||||
|
) {
|
||||||
protected assertChipGroupExist(groupName: string, exist: boolean) {
|
this.assertExist(cy.contains(groupSelector, groupName), exist);
|
||||||
this.assertExist(cy.contains(this.#chipGroup, groupName), exist);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,20 +340,33 @@ export default class PageObject {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected assertChipItemExist(itemName: string, exist: boolean) {
|
|
||||||
cy.get(this.#chipItem).within(() => {
|
|
||||||
cy.contains(itemName).should((exist ? "" : "not.") + "exist");
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected assertChipGroupItemExist(
|
protected assertChipGroupItemExist(
|
||||||
|
groupSelector: string,
|
||||||
groupName: string,
|
groupName: string,
|
||||||
itemName: string,
|
itemName: string,
|
||||||
exist: boolean,
|
exist: boolean,
|
||||||
) {
|
) {
|
||||||
this.assertExist(
|
this.assertExist(
|
||||||
this.#getChipGroup(groupName).contains(this.#chipItem, itemName),
|
this.#getChipGroup(groupSelector, groupName).contains(
|
||||||
|
this.#chipItem,
|
||||||
|
itemName,
|
||||||
|
),
|
||||||
|
exist,
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected assertLabeledChipGroupItemExist(
|
||||||
|
groupSelector: string,
|
||||||
|
labelName: string,
|
||||||
|
itemName: string,
|
||||||
|
exist: boolean,
|
||||||
|
) {
|
||||||
|
this.assertExist(
|
||||||
|
this.#getChipGroupWithLabel(groupSelector, labelName).contains(
|
||||||
|
this.#chipItem,
|
||||||
|
itemName,
|
||||||
|
),
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -200,12 +200,16 @@ export default class AdminEventsTab extends PageObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeResourcePathChipGroup() {
|
public removeResourcePathChipGroup() {
|
||||||
super.removeChipGroup(AdminEventsTabSearchFormFieldsLabel.ResourcePath);
|
super.removeChipGroup(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
|
AdminEventsTabSearchFormFieldsLabel.ResourcePath,
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public assertResourceTypesChipGroupExist(exist: boolean) {
|
public assertResourceTypesChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.ResourceTypes,
|
AdminEventsTabSearchFormFieldsLabel.ResourceTypes,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -214,6 +218,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertOperationTypesChipGroupExist(exist: boolean) {
|
public assertOperationTypesChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.OperationTypes,
|
AdminEventsTabSearchFormFieldsLabel.OperationTypes,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -222,6 +227,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertResourcePathChipGroupExist(exist: boolean) {
|
public assertResourcePathChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.ResourcePath,
|
AdminEventsTabSearchFormFieldsLabel.ResourcePath,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -233,6 +239,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
exist: boolean,
|
exist: boolean,
|
||||||
) {
|
) {
|
||||||
super.assertChipGroupItemExist(
|
super.assertChipGroupItemExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.ResourcePath,
|
AdminEventsTabSearchFormFieldsLabel.ResourcePath,
|
||||||
itemName,
|
itemName,
|
||||||
exist,
|
exist,
|
||||||
|
@ -242,6 +249,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertRealmChipGroupExist(exist: boolean) {
|
public assertRealmChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.Realm,
|
AdminEventsTabSearchFormFieldsLabel.Realm,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -250,6 +258,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertClientChipGroupExist(exist: boolean) {
|
public assertClientChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.Client,
|
AdminEventsTabSearchFormFieldsLabel.Client,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -257,12 +266,17 @@ export default class AdminEventsTab extends PageObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public assertUserChipGroupExist(exist: boolean) {
|
public assertUserChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(AdminEventsTabSearchFormFieldsLabel.User, exist);
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
|
AdminEventsTabSearchFormFieldsLabel.User,
|
||||||
|
exist,
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public assertIpAddressChipGroupExist(exist: boolean) {
|
public assertIpAddressChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.IpAddress,
|
AdminEventsTabSearchFormFieldsLabel.IpAddress,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -271,6 +285,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertDateFromChipGroupExist(exist: boolean) {
|
public assertDateFromChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.DateFrom,
|
AdminEventsTabSearchFormFieldsLabel.DateFrom,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -279,6 +294,7 @@ export default class AdminEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertDateToChipGroupExist(exist: boolean) {
|
public assertDateToChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
AdminEventsTabSearchFormFieldsLabel.DateTo,
|
AdminEventsTabSearchFormFieldsLabel.DateTo,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
|
|
@ -163,6 +163,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public removeEventTypeChipGroupItem(itemName: string) {
|
public removeEventTypeChipGroupItem(itemName: string) {
|
||||||
super.removeChipGroupItem(
|
super.removeChipGroupItem(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.EventType,
|
UserEventsTabSearchFormFieldsLabel.EventType,
|
||||||
itemName,
|
itemName,
|
||||||
);
|
);
|
||||||
|
@ -171,6 +172,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertEventTypeChipGroupItemExist(itemName: string, exist: boolean) {
|
public assertEventTypeChipGroupItemExist(itemName: string, exist: boolean) {
|
||||||
super.assertChipGroupItemExist(
|
super.assertChipGroupItemExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.EventType,
|
UserEventsTabSearchFormFieldsLabel.EventType,
|
||||||
itemName,
|
itemName,
|
||||||
exist,
|
exist,
|
||||||
|
@ -180,6 +182,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertUserIdChipGroupExist(exist: boolean) {
|
public assertUserIdChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.UserId,
|
UserEventsTabSearchFormFieldsLabel.UserId,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -188,6 +191,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertEventTypeChipGroupExist(exist: boolean) {
|
public assertEventTypeChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.EventType,
|
UserEventsTabSearchFormFieldsLabel.EventType,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -196,6 +200,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertClientChipGroupExist(exist: boolean) {
|
public assertClientChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.Client,
|
UserEventsTabSearchFormFieldsLabel.Client,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -204,6 +209,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertDateFromChipGroupExist(exist: boolean) {
|
public assertDateFromChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.DateFrom,
|
UserEventsTabSearchFormFieldsLabel.DateFrom,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -212,6 +218,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertDateToChipGroupExist(exist: boolean) {
|
public assertDateToChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.DateTo,
|
UserEventsTabSearchFormFieldsLabel.DateTo,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
@ -220,6 +227,7 @@ export default class UserEventsTab extends PageObject {
|
||||||
|
|
||||||
public assertIpAddressChipGroupExist(exist: boolean) {
|
public assertIpAddressChipGroupExist(exist: boolean) {
|
||||||
super.assertChipGroupExist(
|
super.assertChipGroupExist(
|
||||||
|
this.genericChipGroupSelector,
|
||||||
UserEventsTabSearchFormFieldsLabel.IpAddress,
|
UserEventsTabSearchFormFieldsLabel.IpAddress,
|
||||||
exist,
|
exist,
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import ListingPage from "../../ListingPage";
|
||||||
|
import UsersPage from "./UsersPage";
|
||||||
|
|
||||||
|
export enum DefaultUserAttribute {
|
||||||
|
username = "Username",
|
||||||
|
email = "Email",
|
||||||
|
firstName = "First name",
|
||||||
|
lastName = "Last name",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UserFilterType {
|
||||||
|
DefaultSearch = "Default search",
|
||||||
|
AttributeSearch = "Attribute search",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UsersListingPage extends ListingPage {
|
||||||
|
#dropdownPanelBtn = "[data-testid='dropdown-panel-btn']";
|
||||||
|
#userAttributeSearchForm = "[data-testid='user-attribute-search-form']";
|
||||||
|
#userAttributeSearchAddFilterBtn =
|
||||||
|
"[data-testid='user-attribute-search-add-filter-button']";
|
||||||
|
#userAttributeSearchBtn = "[data-testid='search-user-attribute-btn']";
|
||||||
|
#usersPage: UsersPage;
|
||||||
|
|
||||||
|
constructor(usersPage: UsersPage) {
|
||||||
|
super();
|
||||||
|
this.#usersPage = usersPage;
|
||||||
|
console.log("this.u1", usersPage);
|
||||||
|
console.log("this.u2", this.#usersPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUserSearchFilter(filter: UserFilterType) {
|
||||||
|
super.selectFilter("user-search-toggle", filter);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
openUserAttributesSearchForm() {
|
||||||
|
cy.get(this.#dropdownPanelBtn).click();
|
||||||
|
cy.get(this.#userAttributeSearchForm).should("be.visible");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addUserAttributeSearchCriteria(
|
||||||
|
defaultUserAttribute: DefaultUserAttribute,
|
||||||
|
attributeValue: string,
|
||||||
|
) {
|
||||||
|
return this.addUserAttributeSearchCriteriaCustom(
|
||||||
|
defaultUserAttribute,
|
||||||
|
attributeValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addUserAttributeSearchCriteriaCustom(
|
||||||
|
attributeLabel: string,
|
||||||
|
attributeValue: string,
|
||||||
|
) {
|
||||||
|
cy.get(this.#userAttributeSearchForm)
|
||||||
|
.find(".pf-m-typeahead")
|
||||||
|
.click()
|
||||||
|
.get(".pf-v5-c-menu__list-item")
|
||||||
|
.contains(attributeLabel)
|
||||||
|
.click({ force: true });
|
||||||
|
|
||||||
|
cy.get(this.#userAttributeSearchForm)
|
||||||
|
.find("#value")
|
||||||
|
.clear()
|
||||||
|
.type(attributeValue);
|
||||||
|
|
||||||
|
cy.get(this.#userAttributeSearchForm)
|
||||||
|
.find(this.#userAttributeSearchAddFilterBtn)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
this.#usersPage.assertAttributeSearchChipExists(
|
||||||
|
attributeLabel,
|
||||||
|
attributeValue,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerAttributesSearch() {
|
||||||
|
cy.get(this.#userAttributeSearchBtn).click();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
import PageObject from "../../components/PageObject";
|
import PageObject from "../../components/PageObject";
|
||||||
import ListingPage from "../../ListingPage";
|
import UsersListingPage from "./UsersListingPage";
|
||||||
|
|
||||||
const listingPage = new ListingPage();
|
|
||||||
|
|
||||||
export default class UsersPage extends PageObject {
|
export default class UsersPage extends PageObject {
|
||||||
#userListTabLink = "listTab";
|
#userListTabLink = "listTab";
|
||||||
#permissionsTabLink = "permissionsTab";
|
#permissionsTabLink = "permissionsTab";
|
||||||
|
#userAttributeSearchChipsGroup =
|
||||||
|
"[data-testid='user-attribute-search-chips-group']";
|
||||||
|
#usersListingPage = new UsersListingPage(this);
|
||||||
|
|
||||||
public goToUserListTab() {
|
public goToUserListTab() {
|
||||||
cy.findByTestId(this.#userListTabLink).click();
|
cy.findByTestId(this.#userListTabLink).click();
|
||||||
|
@ -13,6 +14,10 @@ export default class UsersPage extends PageObject {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public listing() {
|
||||||
|
return this.#usersListingPage;
|
||||||
|
}
|
||||||
|
|
||||||
public goToPermissionsTab() {
|
public goToPermissionsTab() {
|
||||||
cy.findByTestId(this.#permissionsTabLink).click();
|
cy.findByTestId(this.#permissionsTabLink).click();
|
||||||
|
|
||||||
|
@ -20,8 +25,23 @@ export default class UsersPage extends PageObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public goToUserDetailsPage(username: string) {
|
public goToUserDetailsPage(username: string) {
|
||||||
listingPage.searchItem(username);
|
this.#usersListingPage.searchItem(username);
|
||||||
listingPage.goToItemDetails(username);
|
this.#usersListingPage.goToItemDetails(username);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public assertAttributeSearchChipExists(
|
||||||
|
attributeLabel: string,
|
||||||
|
attributeValue: string,
|
||||||
|
exists: boolean,
|
||||||
|
) {
|
||||||
|
super.assertLabeledChipGroupItemExist(
|
||||||
|
this.#userAttributeSearchChipsGroup,
|
||||||
|
attributeLabel,
|
||||||
|
attributeValue,
|
||||||
|
exists,
|
||||||
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,10 +191,18 @@ class AdminClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteUser(username: string) {
|
async deleteUser(username: string, ignoreNonExisting: boolean = false) {
|
||||||
await this.#login();
|
await this.#login();
|
||||||
const user = await this.#client.users.find({ username });
|
const foundUsers = await this.#client.users.find({ username });
|
||||||
await this.#client.users.del({ id: user[0].id! });
|
if (foundUsers.length == 0) {
|
||||||
|
if (ignoreNonExisting) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error(`User not found: ${username}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#client.users.del({ id: foundUsers[0].id! });
|
||||||
}
|
}
|
||||||
|
|
||||||
async createClientScope(scope: ClientScopeRepresentation) {
|
async createClientScope(scope: ClientScopeRepresentation) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
|
||||||
import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import {
|
import {
|
||||||
KeycloakDataTable,
|
KeycloakDataTable,
|
||||||
|
KeycloakSpinner,
|
||||||
ListEmptyState,
|
ListEmptyState,
|
||||||
useAlerts,
|
useAlerts,
|
||||||
useFetch,
|
useFetch,
|
||||||
|
@ -39,9 +39,10 @@ import { toAddUser } from "../../user/routes/AddUser";
|
||||||
import { toUser } from "../../user/routes/User";
|
import { toUser } from "../../user/routes/User";
|
||||||
import { emptyFormatter } from "../../util";
|
import { emptyFormatter } from "../../util";
|
||||||
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
|
||||||
import { KeycloakSpinner } from "@keycloak/keycloak-ui-shared";
|
|
||||||
import { BruteUser, findUsers } from "../role-mapping/resource";
|
import { BruteUser, findUsers } from "../role-mapping/resource";
|
||||||
import { UserDataTableToolbarItems } from "./UserDataTableToolbarItems";
|
import { UserDataTableToolbarItems } from "./UserDataTableToolbarItems";
|
||||||
|
import { UiRealmInfo } from "../../context/auth/uiRealmInfo";
|
||||||
|
import { fetchRealmInfo } from "../../context/auth/admin-ui-endpoint";
|
||||||
|
|
||||||
export type UserAttribute = {
|
export type UserAttribute = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -113,7 +114,7 @@ export function UserDataTable() {
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const { realm: realmName, realmRepresentation: realm } = useRealm();
|
const { realm: realmName, realmRepresentation: realm } = useRealm();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [userStorage, setUserStorage] = useState<ComponentRepresentation[]>();
|
const [uiRealmInfo, setUiRealmInfo] = useState<UiRealmInfo>({});
|
||||||
const [searchUser, setSearchUser] = useState("");
|
const [searchUser, setSearchUser] = useState("");
|
||||||
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
|
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
|
||||||
const [searchType, setSearchType] = useState<SearchType>("default");
|
const [searchType, setSearchType] = useState<SearchType>("default");
|
||||||
|
@ -127,23 +128,17 @@ export function UserDataTable() {
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
const testParams = {
|
|
||||||
type: "org.keycloak.storage.UserStorageProvider",
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await Promise.all([
|
return await Promise.all([
|
||||||
adminClient.components.find(testParams),
|
fetchRealmInfo(adminClient),
|
||||||
adminClient.users.getProfile(),
|
adminClient.users.getProfile(),
|
||||||
]);
|
]);
|
||||||
} catch {
|
} catch {
|
||||||
return [[], {}] as [ComponentRepresentation[], UserProfileConfig];
|
return [{}, {}] as [UiRealmInfo, UserProfileConfig];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
([storageProviders, profile]) => {
|
([uiRealmInfo, profile]) => {
|
||||||
setUserStorage(
|
setUiRealmInfo(uiRealmInfo);
|
||||||
storageProviders.filter((p) => p.config?.enabled?.[0] === "true"),
|
|
||||||
);
|
|
||||||
setProfile(profile);
|
setProfile(profile);
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
@ -171,7 +166,7 @@ export function UserDataTable() {
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (userStorage?.length) {
|
if (uiRealmInfo.userProfileProvidersEnabled) {
|
||||||
addError("noUsersFoundErrorStorage", error);
|
addError("noUsersFoundErrorStorage", error);
|
||||||
} else {
|
} else {
|
||||||
addError("noUsersFoundError", error);
|
addError("noUsersFoundError", error);
|
||||||
|
@ -216,12 +211,12 @@ export function UserDataTable() {
|
||||||
|
|
||||||
const goToCreate = () => navigate(toAddUser({ realm: realmName }));
|
const goToCreate = () => navigate(toAddUser({ realm: realmName }));
|
||||||
|
|
||||||
if (!userStorage || !realm) {
|
if (!uiRealmInfo || !realm) {
|
||||||
return <KeycloakSpinner />;
|
return <KeycloakSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
//should *only* list users when no user federation is configured
|
//should *only* list users when no user federation is configured
|
||||||
const listUsers = !(userStorage.length > 0);
|
const listUsers = !uiRealmInfo.userProfileProvidersEnabled;
|
||||||
|
|
||||||
const clearAllFilters = () => {
|
const clearAllFilters = () => {
|
||||||
const filtered = [...activeFilters].filter(
|
const filtered = [...activeFilters].filter(
|
||||||
|
@ -252,6 +247,7 @@ export function UserDataTable() {
|
||||||
return (
|
return (
|
||||||
<ChipGroup
|
<ChipGroup
|
||||||
className="pf-v5-u-mt-md pf-v5-u-mr-md"
|
className="pf-v5-u-mt-md pf-v5-u-mr-md"
|
||||||
|
data-testid="user-attribute-search-chips-group"
|
||||||
key={entry.name}
|
key={entry.name}
|
||||||
categoryName={
|
categoryName={
|
||||||
entry.displayName.length ? entry.displayName : entry.name
|
entry.displayName.length ? entry.displayName : entry.name
|
||||||
|
|
|
@ -135,7 +135,7 @@ export function UserDataTableAttributeSearchForm({
|
||||||
if (profile) {
|
if (profile) {
|
||||||
return (
|
return (
|
||||||
<KeycloakSelect
|
<KeycloakSelect
|
||||||
data-testid="search-attribute-name"
|
data-testid="search-attribute-name-select"
|
||||||
variant={SelectVariant.typeahead}
|
variant={SelectVariant.typeahead}
|
||||||
onToggle={(isOpen) => setSelectAttributeKeyOpen(isOpen)}
|
onToggle={(isOpen) => setSelectAttributeKeyOpen(isOpen)}
|
||||||
selections={getValues().displayName}
|
selections={getValues().displayName}
|
||||||
|
@ -188,7 +188,10 @@ export function UserDataTableAttributeSearchForm({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className="user-attribute-search-form">
|
<Form
|
||||||
|
className="user-attribute-search-form"
|
||||||
|
data-testid="user-attribute-search-form"
|
||||||
|
>
|
||||||
<TextContent className="user-attribute-search-form-headline">
|
<TextContent className="user-attribute-search-form-headline">
|
||||||
<Text component={TextVariants.h2}>{t("selectAttributes")}</Text>
|
<Text component={TextVariants.h2}>{t("selectAttributes")}</Text>
|
||||||
</TextContent>
|
</TextContent>
|
||||||
|
@ -231,6 +234,7 @@ export function UserDataTableAttributeSearchForm({
|
||||||
</InputGroupItem>
|
</InputGroupItem>
|
||||||
<InputGroupItem>
|
<InputGroupItem>
|
||||||
<Button
|
<Button
|
||||||
|
data-testid="user-attribute-search-add-filter-button"
|
||||||
variant="control"
|
variant="control"
|
||||||
icon={<CheckIcon />}
|
icon={<CheckIcon />}
|
||||||
onClick={addToFilter}
|
onClick={addToFilter}
|
||||||
|
|
|
@ -126,6 +126,7 @@ export function UserDataTableToolbarItems({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownPanel
|
<DropdownPanel
|
||||||
|
data-testid="select-attributes-dropdown"
|
||||||
buttonText={t("selectAttributes")}
|
buttonText={t("selectAttributes")}
|
||||||
setSearchDropdownOpen={setSearchDropdownOpen}
|
setSearchDropdownOpen={setSearchDropdownOpen}
|
||||||
searchDropdownOpen={searchDropdownOpen}
|
searchDropdownOpen={searchDropdownOpen}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import KeycloakAdminClient, {
|
||||||
} from "@keycloak/keycloak-admin-client";
|
} from "@keycloak/keycloak-admin-client";
|
||||||
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
||||||
import { joinPath } from "../../utils/joinPath";
|
import { joinPath } from "../../utils/joinPath";
|
||||||
|
import { UiRealmInfo } from "./uiRealmInfo";
|
||||||
|
|
||||||
export async function fetchAdminUI<T>(
|
export async function fetchAdminUI<T>(
|
||||||
adminClient: KeycloakAdminClient,
|
adminClient: KeycloakAdminClient,
|
||||||
|
@ -27,3 +28,9 @@ export async function fetchAdminUI<T>(
|
||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchRealmInfo(
|
||||||
|
adminClient: KeycloakAdminClient,
|
||||||
|
): Promise<UiRealmInfo> {
|
||||||
|
return fetchAdminUI(adminClient, `ui-ext/info`);
|
||||||
|
}
|
||||||
|
|
5
js/apps/admin-ui/src/context/auth/uiRealmInfo.ts
Normal file
5
js/apps/admin-ui/src/context/auth/uiRealmInfo.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/** Information about a realm, which is available for all admins of the realm */
|
||||||
|
export interface UiRealmInfo {
|
||||||
|
/** Whether at least one user storage provider is enabled */
|
||||||
|
userProfileProvidersEnabled?: boolean;
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ export const SearchDropdown = ({
|
||||||
onOpenChange={(isOpen) => setSearchToggle(isOpen)}
|
onOpenChange={(isOpen) => setSearchToggle(isOpen)}
|
||||||
toggle={(ref) => (
|
toggle={(ref) => (
|
||||||
<MenuToggle
|
<MenuToggle
|
||||||
|
data-testid="user-search-toggle"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id="toggle-id"
|
id="toggle-id"
|
||||||
onClick={() => setSearchToggle(!searchToggle)}
|
onClick={() => setSearchToggle(!searchToggle)}
|
||||||
|
|
|
@ -19,20 +19,30 @@
|
||||||
|
|
||||||
package org.keycloak.admin.ui.rest;
|
package org.keycloak.admin.ui.rest;
|
||||||
|
|
||||||
import jakarta.ws.rs.Consumes;
|
|
||||||
import jakarta.ws.rs.InternalServerErrorException;
|
|
||||||
import jakarta.ws.rs.PUT;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import jakarta.ws.rs.core.Response;
|
|
||||||
import jakarta.ws.rs.core.Response.Status.Family;
|
|
||||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||||
import org.keycloak.admin.ui.rest.model.UIRealmRepresentation;
|
import org.keycloak.admin.ui.rest.model.UIRealmRepresentation;
|
||||||
|
import org.keycloak.admin.ui.rest.model.UIRealmInfo;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.StorageProviderRealmModel;
|
||||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
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.storage.UserStorageProviderModel;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.InternalServerErrorException;
|
||||||
|
import jakarta.ws.rs.PUT;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import jakarta.ws.rs.core.Response.Status.Family;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -67,6 +77,24 @@ public class UIRealmResource {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("info")
|
||||||
|
@Operation(summary = "Gets information about the realm, viewable by all realm admins")
|
||||||
|
@APIResponse(responseCode = "200", description = "", content = {
|
||||||
|
@Content(schema = @Schema(implementation = UIRealmInfo.class, type = SchemaType.OBJECT))})
|
||||||
|
public UIRealmInfo getInfo() {
|
||||||
|
auth.requireAnyAdminRole();
|
||||||
|
|
||||||
|
final var info = new UIRealmInfo();
|
||||||
|
info.setUserProfileProvidersEnabled(isAtLeastOneUserStorageProviderEnabled());
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAtLeastOneUserStorageProviderEnabled() {
|
||||||
|
return ((StorageProviderRealmModel) session.getContext().getRealm()).getUserStorageProvidersStream()
|
||||||
|
.anyMatch(UserStorageProviderModel::isEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateUserProfileConfiguration(UIRealmRepresentation rep) {
|
private void updateUserProfileConfiguration(UIRealmRepresentation rep) {
|
||||||
UPConfig upConfig = rep.getUpConfig();
|
UPConfig upConfig = rep.getUpConfig();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.keycloak.admin.ui.rest.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a realm, which is available for each admin of a realm, not only for admins allowed to view the realm.
|
||||||
|
*/
|
||||||
|
public class UIRealmInfo {
|
||||||
|
private boolean userProfileProvidersEnabled;
|
||||||
|
|
||||||
|
public boolean isUserProfileProvidersEnabled() {
|
||||||
|
return userProfileProvidersEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserProfileProvidersEnabled(final boolean userProfileProvidersEnabled) {
|
||||||
|
this.userProfileProvidersEnabled = userProfileProvidersEnabled;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ package org.keycloak.testsuite.user.profile;
|
||||||
|
|
||||||
import jakarta.ws.rs.client.Client;
|
import jakarta.ws.rs.client.Client;
|
||||||
import jakarta.ws.rs.client.Entity;
|
import jakarta.ws.rs.client.Entity;
|
||||||
|
import jakarta.ws.rs.client.WebTarget;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.Response.Status;
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
|
@ -27,36 +28,100 @@ import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.AfterClass;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.KeycloakBuilder;
|
||||||
import org.keycloak.admin.client.resource.BearerAuthFilter;
|
import org.keycloak.admin.client.resource.BearerAuthFilter;
|
||||||
|
import org.keycloak.admin.client.token.TokenManager;
|
||||||
import org.keycloak.admin.ui.rest.model.UIRealmRepresentation;
|
import org.keycloak.admin.ui.rest.model.UIRealmRepresentation;
|
||||||
|
import org.keycloak.admin.ui.rest.model.UIRealmInfo;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.models.AccountRoles;
|
||||||
|
import org.keycloak.models.AdminRoles;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.userprofile.config.UPAttribute;
|
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||||
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
|
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
|
||||||
import org.keycloak.representations.userprofile.config.UPAttributeRequired;
|
import org.keycloak.representations.userprofile.config.UPAttributeRequired;
|
||||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.util.AssertAdminEvents;
|
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
import org.keycloak.userprofile.config.UPConfigUtils;
|
import org.keycloak.userprofile.config.UPConfigUtils;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author rmartinc
|
* @author rmartinc
|
||||||
*/
|
*/
|
||||||
public class UIRealmResourceTest extends AbstractTestRealmKeycloakTest {
|
public class UIRealmResourceTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
private static final String TEST_PWD = "password";
|
||||||
|
private static final String USER_WITH_VIEW_USERS_ROLE = "user-with-view-users-role";
|
||||||
|
private static final String USER_WITHOUT_ADMIN_ROLE = "user-without-admin-role";
|
||||||
|
|
||||||
|
private static Client httpClient;
|
||||||
|
private static Keycloak keycloakAdminClientViewUsers;
|
||||||
|
private static Keycloak keycloakAdminClientWithoutAdminRoles;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this);
|
public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this);
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void initHttpClients() {
|
||||||
|
httpClient = Keycloak.getClientProvider().newRestEasyClient(null, null, true);
|
||||||
|
|
||||||
|
keycloakAdminClientViewUsers = KeycloakBuilder.builder().serverUrl(getKeycloakServerUrl())
|
||||||
|
.realm(TEST_REALM_NAME)
|
||||||
|
.username(USER_WITH_VIEW_USERS_ROLE)
|
||||||
|
.password(TEST_PWD)
|
||||||
|
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
|
||||||
|
.resteasyClient(httpClient)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
keycloakAdminClientWithoutAdminRoles = KeycloakBuilder.builder().serverUrl(getKeycloakServerUrl())
|
||||||
|
.realm(TEST_REALM_NAME)
|
||||||
|
.username(USER_WITHOUT_ADMIN_ROLE)
|
||||||
|
.password(TEST_PWD)
|
||||||
|
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
|
||||||
|
.resteasyClient(httpClient)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void closeHttpClients() {
|
||||||
|
if (keycloakAdminClientViewUsers != null) {
|
||||||
|
keycloakAdminClientViewUsers.close();
|
||||||
|
}
|
||||||
|
if (keycloakAdminClientWithoutAdminRoles != null) {
|
||||||
|
keycloakAdminClientWithoutAdminRoles.close();
|
||||||
|
}
|
||||||
|
if (httpClient != null) {
|
||||||
|
httpClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
public void configureTestRealm(final RealmRepresentation testRealm) {
|
||||||
|
final var userWithViewUsersRole = createTestUserRep(USER_WITH_VIEW_USERS_ROLE,
|
||||||
|
Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.VIEW_USERS);
|
||||||
|
testRealm.getUsers().add(userWithViewUsersRole);
|
||||||
|
|
||||||
|
final var userWithoutAdminRole = createTestUserRep(USER_WITHOUT_ADMIN_ROLE,
|
||||||
|
Constants.ACCOUNT_MANAGEMENT_CLIENT_ID, AccountRoles.VIEW_GROUPS);
|
||||||
|
testRealm.getUsers().add(userWithoutAdminRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -89,55 +154,112 @@ public class UIRealmResourceTest extends AbstractTestRealmKeycloakTest {
|
||||||
AdminEventRepresentation adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
AdminEventRepresentation adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
Assert.assertNotNull(adminEvent.getRepresentation());
|
Assert.assertNotNull(adminEvent.getRepresentation());
|
||||||
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
upConfig.getAttribute("foo").setDisplayName("Foo");
|
upConfig.getAttribute("foo").setDisplayName("Foo");
|
||||||
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
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);
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
upConfig.getAttribute("foo").setPermissions(new UPAttributePermissions(Set.of(), Set.of(UPConfigUtils.ROLE_USER)));
|
upConfig.getAttribute("foo").setPermissions(new UPAttributePermissions(Set.of(), Set.of(UPConfigUtils.ROLE_USER)));
|
||||||
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
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);
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
upConfig.getAttribute("foo").setRequired(new UPAttributeRequired(Set.of(UPConfigUtils.ROLE_ADMIN, UPConfigUtils.ROLE_USER), Set.of()));
|
upConfig.getAttribute("foo").setRequired(new UPAttributeRequired(Set.of(UPConfigUtils.ROLE_ADMIN, UPConfigUtils.ROLE_USER), Set.of()));
|
||||||
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
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);
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
upConfig.getAttribute("foo").setValidations(Map.of("length", Map.of("min", "3", "max", "128")));
|
upConfig.getAttribute("foo").setValidations(Map.of("length", Map.of("min", "3", "max", "128")));
|
||||||
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
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);
|
adminEvent = assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, "ui-ext", ResourceType.USER_PROFILE);
|
||||||
Assert.assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
assertEquals(upConfig, toUpConfig(adminEvent.getRepresentation()));
|
||||||
|
|
||||||
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
updateRealmExt(toUIRealmRepresentation(rep, upConfig));
|
||||||
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
assertAdminEvents.assertEvent(TEST_REALM_NAME, OperationType.UPDATE, Matchers.nullValue(String.class), ResourceType.REALM);
|
||||||
assertAdminEvents.assertEmpty();
|
assertAdminEvents.assertEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void uiRealmInfoSucceedsWithAnyAdminRole() {
|
||||||
|
final var response = getUiRealmInfo(keycloakAdminClientViewUsers.tokenManager());
|
||||||
|
|
||||||
|
assertEquals(Status.OK.getStatusCode(), response.getStatus());
|
||||||
|
final var responseStr = response.readEntity(String.class);
|
||||||
|
final var uiRealmInfo = toUiRealmInfo(responseStr);
|
||||||
|
assertNotNull(uiRealmInfo);
|
||||||
|
assertFalse(uiRealmInfo.isUserProfileProvidersEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void uiRealmInfoFailsWhenNoAdminRoleIsAssigned() {
|
||||||
|
final var response = getUiRealmInfo(keycloakAdminClientWithoutAdminRoles.tokenManager());
|
||||||
|
|
||||||
|
assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getKeycloakServerUrl() {
|
||||||
|
return getAuthServerContextRoot() + "/auth";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UserRepresentation createTestUserRep(final String username, final String clientId, final String roleName) {
|
||||||
|
return UserBuilder.create().enabled(true)
|
||||||
|
.username(username)
|
||||||
|
.email(username + "@localhost")
|
||||||
|
.firstName(username + "-first")
|
||||||
|
.lastName(username + "-last")
|
||||||
|
.password(TEST_PWD)
|
||||||
|
.role(clientId, roleName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getUiRealmInfo(final TokenManager tokenManager) {
|
||||||
|
return prepareHttpRequest(TEST_REALM_NAME, "ui-ext/info", tokenManager)
|
||||||
|
.request(MediaType.APPLICATION_JSON)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateRealmExt(UIRealmRepresentation rep) {
|
private void updateRealmExt(UIRealmRepresentation rep) {
|
||||||
try (Client client = Keycloak.getClientProvider().newRestEasyClient(null, null, true)) {
|
final var realmName = rep.getRealm();
|
||||||
Response response = client.target(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth")
|
final var request = prepareHttpRequest(realmName, "ui-ext", adminClient.tokenManager());
|
||||||
.path("/admin/realms/" + rep.getRealm() + "/ui-ext")
|
|
||||||
.register(new BearerAuthFilter(adminClient.tokenManager()))
|
final var response = request
|
||||||
.request(MediaType.APPLICATION_JSON)
|
.request(MediaType.APPLICATION_JSON)
|
||||||
.put(Entity.entity(rep, MediaType.APPLICATION_JSON));
|
.put(Entity.entity(rep, MediaType.APPLICATION_JSON));
|
||||||
Assert.assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
|
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WebTarget prepareHttpRequest(final String realmName, final String subPath, final TokenManager tokenManager) {
|
||||||
|
final var realmAdminPath = "/admin/realms/" + realmName;
|
||||||
|
return httpClient.target(getKeycloakServerUrl())
|
||||||
|
.path(realmAdminPath + "/" + subPath)
|
||||||
|
.register(new BearerAuthFilter(tokenManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
private UIRealmRepresentation toUIRealmRepresentation(RealmRepresentation realm, UPConfig upConfig) throws IOException {
|
private UIRealmRepresentation toUIRealmRepresentation(RealmRepresentation realm, UPConfig upConfig) throws IOException {
|
||||||
UIRealmRepresentation uiRealm = JsonSerialization.readValue(JsonSerialization.writeValueAsString(realm), UIRealmRepresentation.class);
|
UIRealmRepresentation uiRealm = deserialize(JsonSerialization.writeValueAsString(realm), UIRealmRepresentation.class);
|
||||||
uiRealm.setUpConfig(upConfig);
|
uiRealm.setUpConfig(upConfig);
|
||||||
return uiRealm;
|
return uiRealm;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UPConfig toUpConfig(String representation) throws IOException {
|
private UPConfig toUpConfig(final String representation) {
|
||||||
return JsonSerialization.readValue(representation, UPConfig.class);
|
return deserialize(representation, UPConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UIRealmInfo toUiRealmInfo(final String representation) {
|
||||||
|
return deserialize(representation, UIRealmInfo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T deserialize(final String representation, final Class<T> type) {
|
||||||
|
try {
|
||||||
|
return JsonSerialization.readValue(representation, type);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue