Enable user profile by default

closes #25151

Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
mposolda 2023-12-19 09:25:54 +01:00 committed by Pedro Igor
parent 01cd645668
commit 692aeee17d
109 changed files with 1632 additions and 2281 deletions

View file

@ -214,7 +214,7 @@ jobs:
- name: Start Keycloak server - name: Start Keycloak server
run: | run: |
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=declarative-user-profile,transient-users &> ~/server.log & keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=transient-users &> ~/server.log &
env: env:
KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin KEYCLOAK_ADMIN_PASSWORD: admin
@ -297,7 +297,7 @@ jobs:
- name: Start Keycloak server - name: Start Keycloak server
run: | run: |
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz,declarative-user-profile,transient-users &> ~/server.log & keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz,transient-users &> ~/server.log &
env: env:
KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin KEYCLOAK_ADMIN_PASSWORD: admin

View file

@ -73,8 +73,6 @@ public class Profile {
PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT), PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT),
DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW),
DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL), DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL),
CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW), CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW),

View file

@ -79,7 +79,6 @@ public class ProfileTest {
Profile.Feature.RECOVERY_CODES, Profile.Feature.RECOVERY_CODES,
Profile.Feature.SCRIPTS, Profile.Feature.SCRIPTS,
Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.TOKEN_EXCHANGE,
Profile.Feature.DECLARATIVE_USER_PROFILE,
Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.CLIENT_SECRET_ROTATION,
Profile.Feature.UPDATE_EMAIL, Profile.Feature.UPDATE_EMAIL,
Profile.Feature.LINKEDIN_OAUTH Profile.Feature.LINKEDIN_OAUTH
@ -90,7 +89,7 @@ public class ProfileTest {
disabledFeatures.add(Profile.Feature.KERBEROS); disabledFeatures.add(Profile.Feature.KERBEROS);
} }
assertEquals(profile.getDisabledFeatures(), disabledFeatures); assertEquals(profile.getDisabledFeatures(), disabledFeatures);
assertEquals(profile.getPreviewFeatures(), Profile.Feature.ACCOUNT3, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.MULTI_SITE, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL, Profile.Feature.DPOP); assertEquals(profile.getPreviewFeatures(), Profile.Feature.ACCOUNT3, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.MULTI_SITE, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL, Profile.Feature.DPOP);
} }
@Test @Test

View file

@ -40,15 +40,8 @@ describe("Realm settings tabs tests", () => {
return this; return this;
}; };
it("shows the 'user profile' tab if enabled", () => { it("shows the 'user profile' tab", () => {
sidebarPage.goToRealmSettings(); sidebarPage.goToRealmSettings();
cy.findByTestId(realmSettingsPage.userProfileTab).should("not.exist");
realmSettingsPage.toggleSwitch(
realmSettingsPage.profileEnabledSwitch,
false,
);
realmSettingsPage.save(realmSettingsPage.generalSaveBtn);
masthead.checkNotificationMessage("Realm successfully updated");
cy.findByTestId(realmSettingsPage.userProfileTab).should("exist"); cy.findByTestId(realmSettingsPage.userProfileTab).should("exist");
}); });

View file

@ -1,156 +0,0 @@
import { v4 as uuid } from "uuid";
import Masthead from "../support/pages/admin-ui/Masthead";
import SidebarPage from "../support/pages/admin-ui/SidebarPage";
import LoginPage from "../support/pages/LoginPage";
import adminClient from "../support/util/AdminClient";
import { keycloakBefore } from "../support/util/keycloak_hooks";
import CreateUserPage from "../support/pages/admin-ui/manage/users/CreateUserPage";
import RealmSettingsPage from "../support/pages/admin-ui/manage/realm_settings/RealmSettingsPage";
import ListingPage from "../support/pages/admin-ui/ListingPage";
const loginPage = new LoginPage();
const sidebarPage = new SidebarPage();
const realmSettingsPage = new RealmSettingsPage();
const createUserPage = new CreateUserPage();
const masthead = new Masthead();
const listingPage = new ListingPage();
describe("User profile disabled", () => {
const realmName = "Realm_" + uuid();
before(async () => {
await adminClient.createRealm(realmName, {
attributes: { userProfileEnabled: "false" },
});
});
after(async () => {
await adminClient.deleteRealm(realmName);
});
beforeEach(() => {
loginPage.logIn();
keycloakBefore();
sidebarPage.goToRealm(realmName);
});
afterEach(() => {
sidebarPage.goToRealmSettings();
realmSettingsPage.goToLoginTab();
cy.wait(1000);
cy.findByTestId("email-as-username-switch").uncheck({ force: true });
cy.findByTestId("edit-username-switch").uncheck({ force: true });
});
it("Create user with email as username, edit username and user profile disabled", () => {
// Ensure email as username and edit username are disabled
sidebarPage.goToRealmSettings();
realmSettingsPage.goToLoginTab();
cy.findByTestId("email-as-username-switch").should("have.value", "off");
cy.findByTestId("edit-username-switch").should("have.value", "off");
// Create user
sidebarPage.goToUsers();
createUserPage.goToCreateUser();
createUserPage.createUser("testuser1");
createUserPage.save();
masthead.checkNotificationMessage("The user has been created");
// Check that username is readonly
cy.get("#kc-username").should("have.attr", "readonly");
});
it("Create user with email as username enabled, edit username disabled and user profile disabled", () => {
// Create user and check that username is not visible and that email is editable
sidebarPage.goToUsers();
createUserPage.goToCreateUser();
createUserPage.createUser("testuser2");
createUserPage.save();
masthead.checkNotificationMessage("The user has been created");
// Ensure email as username is enabled and edit username are disabled
sidebarPage.goToRealmSettings();
realmSettingsPage.goToLoginTab();
cy.findByTestId("email-as-username-switch").check({ force: true });
cy.findByTestId("email-as-username-switch").should("have.value", "on");
cy.findByTestId("edit-username-switch").should("have.value", "off");
//Find user and do some checks
sidebarPage.goToUsers();
sidebarPage.waitForPageLoad();
listingPage.goToItemDetails("testuser2");
cy.findByTestId("view-header").should("contain.text", "testuser2");
cy.get("#kc-username").should("not.exist");
cy.findByTestId("email-input").type("testuser1@gmail.com");
cy.findByTestId("save-user").click();
masthead.checkNotificationMessage("The user has been saved");
cy.findByTestId("view-header").should(
"contain.text",
"testuser1@gmail.com",
);
cy.findByTestId("email-input").clear();
cy.findByTestId("email-input").type("testuser2@gmail.com");
cy.findByTestId("save-user").click();
masthead.checkNotificationMessage("The user has been saved");
cy.findByTestId("view-header").should(
"contain.text",
"testuser2@gmail.com",
);
});
it("Create user with email as username disabled, edit username enabled and user profile disabled", () => {
// Ensure email as username is disabled and edit username is enabled
sidebarPage.goToRealmSettings();
realmSettingsPage.goToLoginTab();
cy.findByTestId("email-as-username-switch").should("have.value", "off");
cy.findByTestId("edit-username-switch").check({ force: true });
cy.findByTestId("edit-username-switch").should("have.value", "on");
// Create user and check that username is visible and that email is editable
sidebarPage.goToUsers();
createUserPage.goToCreateUser();
cy.get("#kc-username").type("testuser3");
cy.findByTestId("email-input").type("testuser3@test.com");
cy.findByTestId("create-user").click();
masthead.checkNotificationMessage("The user has been created");
cy.findByTestId("email-input").clear();
cy.findByTestId("email-input").type("testuser4@test.com");
cy.findByTestId("save-user").click();
masthead.checkNotificationMessage("The user has been saved");
cy.findByTestId("view-header").should("contain.text", "testuser3");
});
it("Create user with email as username, edit username enabled and user profile disabled", () => {
// Create user and check that username is not visible and that email is editable
sidebarPage.goToUsers();
createUserPage.goToCreateUser();
createUserPage.createUser("testuser5");
cy.findByTestId("email-input").type("testuser5@gmail.com");
createUserPage.save();
masthead.checkNotificationMessage("The user has been created");
sidebarPage.goToRealmSettings();
realmSettingsPage.goToLoginTab();
cy.findByTestId("edit-username-switch").check({ force: true });
cy.findByTestId("edit-username-switch").should("have.value", "on");
cy.findByTestId("email-as-username-switch").check({ force: true });
cy.findByTestId("email-as-username-switch").should("have.value", "on");
//Find user and do some checks
sidebarPage.goToUsers();
sidebarPage.waitForPageLoad();
listingPage.goToItemDetails("testuser5");
cy.findByTestId("view-header").should("contain.text", "testuser5");
cy.get("#kc-username").should("not.exist");
cy.findByTestId("email-input").clear();
cy.findByTestId("email-input").type("testuser6@test.com");
cy.findByTestId("save-user").click();
cy.findByTestId("view-header").should("contain.text", "testuser6@test.com");
masthead.checkNotificationMessage("The user has been saved");
});
});

View file

@ -31,12 +31,7 @@ describe("User profile tabs", () => {
const realmName = "Realm_" + uuid(); const realmName = "Realm_" + uuid();
const attributeName = "Test"; const attributeName = "Test";
before(() => before(() => adminClient.createRealm(realmName));
adminClient.createRealm(realmName, {
attributes: { userProfileEnabled: "true" },
}),
);
after(() => adminClient.deleteRealm(realmName)); after(() => adminClient.deleteRealm(realmName));
beforeEach(() => { beforeEach(() => {

View file

@ -2,6 +2,7 @@ import { v4 as uuid } from "uuid";
import SidebarPage from "../support/pages/admin-ui/SidebarPage"; import SidebarPage from "../support/pages/admin-ui/SidebarPage";
import LoginPage from "../support/pages/LoginPage"; import LoginPage from "../support/pages/LoginPage";
import RealmSettingsPage from "../support/pages/admin-ui/manage/realm_settings/RealmSettingsPage";
import CreateUserPage from "../support/pages/admin-ui/manage/users/CreateUserPage"; import CreateUserPage from "../support/pages/admin-ui/manage/users/CreateUserPage";
import Masthead from "../support/pages/admin-ui/Masthead"; import Masthead from "../support/pages/admin-ui/Masthead";
import ListingPage from "../support/pages/admin-ui/ListingPage"; import ListingPage from "../support/pages/admin-ui/ListingPage";
@ -23,6 +24,7 @@ let groupsList: string[] = [];
describe("User creation", () => { describe("User creation", () => {
const loginPage = new LoginPage(); const loginPage = new LoginPage();
const sidebarPage = new SidebarPage(); const sidebarPage = new SidebarPage();
const realmSettingsPage = new RealmSettingsPage();
const createUserPage = new CreateUserPage(); const createUserPage = new CreateUserPage();
const userGroupsPage = new UserGroupsPage(); const userGroupsPage = new UserGroupsPage();
const masthead = new Masthead(); const masthead = new Masthead();
@ -30,7 +32,7 @@ describe("User creation", () => {
const listingPage = new ListingPage(); const listingPage = new ListingPage();
const userDetailsPage = new UserDetailsPage(); const userDetailsPage = new UserDetailsPage();
const credentialsPage = new CredentialsPage(); const credentialsPage = new CredentialsPage();
const attributesTab = new AttributesTab(); const attributesTab = new AttributesTab(true);
const usersPage = new UsersPage(); const usersPage = new UsersPage();
const identityProviderLinksTab = new IdentityProviderLinksTab(); const identityProviderLinksTab = new IdentityProviderLinksTab();
@ -143,6 +145,14 @@ describe("User creation", () => {
listingPage.searchItem(itemId).itemExist(itemId); listingPage.searchItem(itemId).itemExist(itemId);
}); });
it("Select Unmanaged attributes", () => {
sidebarPage.goToRealmSettings();
sidebarPage.waitForPageLoad();
realmSettingsPage.fillUnmanagedAttributes("Enabled");
realmSettingsPage.save(realmSettingsPage.generalSaveBtn);
masthead.checkNotificationMessage("Realm successfully updated", true);
});
it("User attributes test", () => { it("User attributes test", () => {
listingPage.goToItemDetails(itemId); listingPage.goToItemDetails(itemId);

View file

@ -1,11 +1,25 @@
export default class AttributesTab { export default class AttributesTab {
#saveAttributeBtn = "save-attributes"; #saveAttributeBtn = "save-attributes";
#addAttributeBtn = "attributes-add-row";
#attributesTab = "attributes"; #attributesTab = "attributes";
#keyInput = "attributes-key";
#valueInput = "attributes-value";
#removeBtn = "attributes-remove";
#emptyState = "attributes-empty-state"; #emptyState = "attributes-empty-state";
#addAttributeBtn: string;
#keyInput: string;
#valueInput: string;
#removeBtn: string;
constructor(isForUser = false) {
if (isForUser) {
this.#addAttributeBtn = "unmanagedAttributes-add-row";
this.#keyInput = "unmanagedAttributes-key";
this.#valueInput = "unmanagedAttributes-value";
this.#removeBtn = "unmanagedAttributes-remove";
} else {
this.#addAttributeBtn = "attributes-add-row";
this.#keyInput = "attributes-key";
this.#valueInput = "attributes-value";
this.#removeBtn = "attributes-remove";
}
}
public goToAttributesTab() { public goToAttributesTab() {
cy.findByTestId(this.#attributesTab).click(); cy.findByTestId(this.#attributesTab).click();

View file

@ -67,7 +67,6 @@ export default class RealmSettingsPage extends CommonPage {
supportedLocalesToggle = "#kc-l-supported-locales"; supportedLocalesToggle = "#kc-l-supported-locales";
emailSaveBtn = "email-tab-save"; emailSaveBtn = "email-tab-save";
managedAccessSwitch = "user-managed-access-switch"; managedAccessSwitch = "user-managed-access-switch";
profileEnabledSwitch = "user-profile-enabled-switch";
userRegSwitch = "user-reg-switch"; userRegSwitch = "user-reg-switch";
forgotPwdSwitch = "forgot-pw-switch"; forgotPwdSwitch = "forgot-pw-switch";
rememberMeSwitch = "remember-me-switch"; rememberMeSwitch = "remember-me-switch";
@ -233,6 +232,7 @@ export default class RealmSettingsPage extends CommonPage {
#realmDisplayName = "#kc-display-name"; #realmDisplayName = "#kc-display-name";
#frontEndURL = "#kc-frontend-url"; #frontEndURL = "#kc-frontend-url";
#requireSSL = "#kc-require-ssl"; #requireSSL = "#kc-require-ssl";
#unmanagedAttributes = "#kc-user-profile-unmanaged-attribute-policy";
#fromDisplayName = "from-display-name"; #fromDisplayName = "from-display-name";
#replyToEmail = "#kc-reply-to"; #replyToEmail = "#kc-reply-to";
#port = "#kc-port"; #port = "#kc-port";
@ -319,6 +319,12 @@ export default class RealmSettingsPage extends CommonPage {
return this; return this;
} }
getUnmanagedAttributes(option: string) {
cy.get(this.#unmanagedAttributes).contains(option);
return this;
}
fillDisplayName(displayName: string) { fillDisplayName(displayName: string) {
cy.get(this.#realmDisplayName).clear().type(displayName); cy.get(this.#realmDisplayName).clear().type(displayName);
} }
@ -355,6 +361,14 @@ export default class RealmSettingsPage extends CommonPage {
.click(); .click();
} }
fillUnmanagedAttributes(option: string) {
cy.get(this.#unmanagedAttributes)
.click()
.get(".pf-c-select__menu-item")
.contains(option)
.click();
}
setDefaultLocale(locale: string) { setDefaultLocale(locale: string) {
cy.get(this.selectDefaultLocale).click(); cy.get(this.selectDefaultLocale).click();
cy.findByTestId(this.defaultLocaleList).contains(locale).click(); cy.findByTestId(this.defaultLocaleList).contains(locale).click();

View file

@ -9,7 +9,7 @@ export default class CreateUserPage {
cancelBtn: string; cancelBtn: string;
constructor() { constructor() {
this.usernameInput = "#kc-username"; this.usernameInput = "#username";
this.usersEmptyState = "empty-state"; this.usersEmptyState = "empty-state";
this.emptyStateCreateUserBtn = "no-users-found-empty-action"; this.emptyStateCreateUserBtn = "no-users-found-empty-action";

View file

@ -21,11 +21,11 @@ export default class UserDetailsPage extends PageObject {
super(); super();
this.saveBtn = "save-user"; this.saveBtn = "save-user";
this.cancelBtn = "cancel-create-user"; this.cancelBtn = "cancel-create-user";
this.emailInput = "email-input"; this.emailInput = "email";
this.emailValue = () => "example" + "_" + uuid() + "@example.com"; this.emailValue = () => "example" + "_" + uuid() + "@example.com";
this.firstNameInput = "firstName-input"; this.firstNameInput = "firstName";
this.firstNameValue = "firstname"; this.firstNameValue = "firstname";
this.lastNameInput = "lastName-input"; this.lastNameInput = "lastName";
this.lastNameValue = "lastname"; this.lastNameValue = "lastname";
this.requiredUserActions = [RequiredActionAlias.UPDATE_PASSWORD]; this.requiredUserActions = [RequiredActionAlias.UPDATE_PASSWORD];
this.identityProviderLinksTab = "identity-provider-links-tab"; this.identityProviderLinksTab = "identity-provider-links-tab";

View file

@ -1461,7 +1461,6 @@ exactSearch=Exact search
value=Value value=Value
filenamePlaceholder=Upload a PEM file or paste key below filenamePlaceholder=Upload a PEM file or paste key below
deleteConfirm_one=Are you sure you want to delete this group '{{groupName}}'. deleteConfirm_one=Are you sure you want to delete this group '{{groupName}}'.
userProfileEnabledHelp=If enabled, allows managing user profiles.
scopeDisplayNameHelp=A unique name for this scope. The name can be used to uniquely identify a scope, useful when querying for a specific scope. scopeDisplayNameHelp=A unique name for this scope. The name can be used to uniquely identify a scope, useful when querying for a specific scope.
times.seconds=Seconds times.seconds=Seconds
removeMappingTitle=Remove role? removeMappingTitle=Remove role?
@ -1890,7 +1889,6 @@ moveToGroup=Move {{group1}} to {{group2}}
noRealmRoles=No realm roles noRealmRoles=No realm roles
events-disable-confirm=If "Save events" is disabled, subsequent events will not be displayed in the "Events" menu events-disable-confirm=If "Save events" is disabled, subsequent events will not be displayed in the "Events" menu
reqAuthnConstraints=Requested AuthnContext Constraints reqAuthnConstraints=Requested AuthnContext Constraints
userProfileEnabled=User Profile Enabled
eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Pushed authorization request eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Pushed authorization request
addIdpMapperNameHelp=Name of the mapper. addIdpMapperNameHelp=Name of the mapper.
requirements.ALTERNATIVE=Alternative requirements.ALTERNATIVE=Alternative

View file

@ -1437,7 +1437,6 @@ exactSearch=Búsqueda exacta
value=Valor value=Valor
filenamePlaceholder=Cargar un archivo PEM o pegar la clave a continuación filenamePlaceholder=Cargar un archivo PEM o pegar la clave a continuación
deleteConfirm_one=¿Estás seguro de que quieres eliminar este grupo '{{groupName}}'? deleteConfirm_one=¿Estás seguro de que quieres eliminar este grupo '{{groupName}}'?
userProfileEnabledHelp=Si está habilitado, permite gestionar perfiles de usuario.
scopeDisplayNameHelp=Un nombre único para este alcance. El nombre puede ser utilizado para identificar un alcance de manera única, útil al buscar un alcance específico. scopeDisplayNameHelp=Un nombre único para este alcance. El nombre puede ser utilizado para identificar un alcance de manera única, útil al buscar un alcance específico.
times.seconds=Segundos times.seconds=Segundos
removeMappingTitle=¿Eliminar asignación? removeMappingTitle=¿Eliminar asignación?
@ -1865,7 +1864,6 @@ moveToGroup=Mover {{grupo1}} a {{grupo2}}
noRealmRoles=No hay roles de reino noRealmRoles=No hay roles de reino
events-disable-confirm=Si se deshabilita "Guardar eventos", los eventos posteriores no se mostrarán en el menú "Eventos" events-disable-confirm=Si se deshabilita "Guardar eventos", los eventos posteriores no se mostrarán en el menú "Eventos"
reqAuthnConstraints=Restricciones de contexto de autenticación solicitadas reqAuthnConstraints=Restricciones de contexto de autenticación solicitadas
userProfileEnabled=Perfil de usuario habilitado
eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Solicitud de autorización empujada eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Solicitud de autorización empujada
addIdpMapperNameHelp=Nombre del mapeador. addIdpMapperNameHelp=Nombre del mapeador.
requirements.ALTERNATIVE=Alternativa requirements.ALTERNATIVE=Alternativa

View file

@ -1437,7 +1437,6 @@ exactSearch=Wyszukiwanie dokładne
value=Wartość value=Wartość
filenamePlaceholder=Prześlij plik PEM lub wklej klucz poniżej filenamePlaceholder=Prześlij plik PEM lub wklej klucz poniżej
deleteConfirm_one=Czy na pewno chcesz usunąć tę grupę '{{groupName}}'? deleteConfirm_one=Czy na pewno chcesz usunąć tę grupę '{{groupName}}'?
userProfileEnabledHelp=Jeśli włączone, umożliwia zarządzanie profilami użytkowników.
scopeDisplayNameHelp=Unikalna nazwa tego zakresu. Nazwa może być używana do jednoznacznego zidentyfikowania zakresu, co jest przydatne podczas wyszukiwania konkretnego zakresu. scopeDisplayNameHelp=Unikalna nazwa tego zakresu. Nazwa może być używana do jednoznacznego zidentyfikowania zakresu, co jest przydatne podczas wyszukiwania konkretnego zakresu.
times.seconds=Sekundy times.seconds=Sekundy
removeMappingTitle=Usuń rolę? removeMappingTitle=Usuń rolę?
@ -1865,7 +1864,6 @@ moveToGroup=Przenieś {{group1}} do {{group2}}
noRealmRoles=Brak ról obszaru noRealmRoles=Brak ról obszaru
events-disable-confirm=Jeśli "Zapisz zdarzenia" jest wyłączone, kolejne zdarzenia nie będą wyświetlane w menu "Zdarzenia" events-disable-confirm=Jeśli "Zapisz zdarzenia" jest wyłączone, kolejne zdarzenia nie będą wyświetlane w menu "Zdarzenia"
reqAuthnConstraints=Wymagane ograniczenia AuthnContext reqAuthnConstraints=Wymagane ograniczenia AuthnContext
userProfileEnabled=Profil użytkownika włączony
eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Żądanie autoryzacji przesuniętej eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Żądanie autoryzacji przesuniętej
addIdpMapperNameHelp=Nazwa mappera. addIdpMapperNameHelp=Nazwa mappera.
requirements.ALTERNATIVE=Alternatywa requirements.ALTERNATIVE=Alternatywa

View file

@ -1412,7 +1412,6 @@ exactSearch=精确搜索
value=数值 value=数值
filenamePlaceholder=上传 PEM 文件或在下方粘贴密钥 filenamePlaceholder=上传 PEM 文件或在下方粘贴密钥
deleteConfirm_one=是否要删除此群组“{{groupName}}”。 deleteConfirm_one=是否要删除此群组“{{groupName}}”。
userProfileEnabledHelp=如果启用,允许管理用户配置文件。
times.seconds= times.seconds=
removeMappingTitle=移除角色? removeMappingTitle=移除角色?
executorTypeSelectAlgorithm=执行器类型选择算法 executorTypeSelectAlgorithm=执行器类型选择算法
@ -1831,7 +1830,6 @@ moveToGroup=将{{group1}}移动到{{group2}}
noRealmRoles=无领域角色 noRealmRoles=无领域角色
events-disable-confirm=如果禁用“保存事件”,后续事件将不会展示在“事件”菜单中。 events-disable-confirm=如果禁用“保存事件”,后续事件将不会展示在“事件”菜单中。
reqAuthnConstraints=请求的上下文约束 reqAuthnConstraints=请求的上下文约束
userProfileEnabled=用户资料
requirements.ALTERNATIVE=非必需 requirements.ALTERNATIVE=非必需
credentialResetConfirm=发送电子邮件 credentialResetConfirm=发送电子邮件
permissionsEnabledHelp=确定是否启用细粒度权限来管理此角色。禁用将删除所有已设置的当前权限。 permissionsEnabledHelp=确定是否启用细粒度权限来管理此角色。禁用将删除所有已设置的当前权限。

View file

@ -28,7 +28,6 @@ import {
convertAttributeNameToForm, convertAttributeNameToForm,
convertToFormValues, convertToFormValues,
} from "../util"; } from "../util";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { import {
UnmanagedAttributePolicy, UnmanagedAttributePolicy,
UserProfileConfig, UserProfileConfig,
@ -57,7 +56,6 @@ export const RealmSettingsGeneralTab = ({
setValue, setValue,
formState: { isDirty, errors }, formState: { isDirty, errors },
} = form; } = form;
const isFeatureEnabled = useIsFeatureEnabled();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const requireSslTypes = ["all", "external", "none"]; const requireSslTypes = ["all", "external", "none"];
@ -72,7 +70,6 @@ export const RealmSettingsGeneralTab = ({
]; ];
const [isUnmanagedAttributeOpen, setIsUnmanagedAttributeOpen] = const [isUnmanagedAttributeOpen, setIsUnmanagedAttributeOpen] =
useState(false); useState(false);
const [isUserProfileEnabled, setUserProfileEnabled] = useState(false);
const setupForm = () => { const setupForm = () => {
convertToFormValues(realm, setValue); convertToFormValues(realm, setValue);
@ -86,7 +83,6 @@ export const RealmSettingsGeneralTab = ({
result, result,
); );
} }
setUserProfileEnabled(realm.attributes?.["userProfileEnabled"] === "true");
}; };
useFetch( useFetch(
@ -251,79 +247,40 @@ export const RealmSettingsGeneralTab = ({
)} )}
/> />
</FormGroup> </FormGroup>
{isFeatureEnabled(Feature.DeclarativeUserProfile) && ( <FormGroup
<FormGroup label={t("unmanagedAttributes")}
hasNoPaddingTop fieldId="kc-user-profile-unmanaged-attribute-policy"
label={t("userProfileEnabled")} labelIcon={
labelIcon={ <HelpItem
<HelpItem helpText={t("unmanagedAttributesHelpText")}
helpText={t("userProfileEnabledHelp")} fieldLabelId="unmanagedAttributes"
fieldLabelId="userProfileEnabled"
/>
}
fieldId="kc-user-profile-enabled"
>
<Controller
name={
convertAttributeNameToForm(
"attributes.userProfileEnabled",
) as any
}
control={control}
defaultValue="false"
render={({ field }) => (
<Switch
id="kc-user-profile-enabled"
data-testid="user-profile-enabled-switch"
label={t("on")}
labelOff={t("off")}
isChecked={field.value === "true"}
onChange={(value) => {
field.onChange(value.toString());
setUserProfileEnabled(value);
}}
aria-label={t("userProfileEnabled")}
/>
)}
/> />
</FormGroup> }
)} >
{isUserProfileEnabled && ( <Select
<FormGroup toggleId="kc-user-profile-unmanaged-attribute-policy"
label={t("unmanagedAttributes")} onToggle={() =>
fieldId="kc-user-profile-unmanaged-attribute-policy" setIsUnmanagedAttributeOpen(!isUnmanagedAttributeOpen)
labelIcon={
<HelpItem
helpText={t("unmanagedAttributesHelpText")}
fieldLabelId="unmanagedAttributes"
/>
} }
> onSelect={(_, value) => {
<Select if (userProfileConfig) {
toggleId="kc-user-profile-unmanaged-attribute-policy" userProfileConfig.unmanagedAttributePolicy =
onToggle={() => value as UnmanagedAttributePolicy;
setIsUnmanagedAttributeOpen(!isUnmanagedAttributeOpen) setUserProfileConfig(userProfileConfig);
} }
onSelect={(_, value) => { setIsUnmanagedAttributeOpen(false);
if (userProfileConfig) { }}
userProfileConfig.unmanagedAttributePolicy = selections={userProfileConfig?.unmanagedAttributePolicy}
value as UnmanagedAttributePolicy; variant={SelectVariant.single}
setUserProfileConfig(userProfileConfig); isOpen={isUnmanagedAttributeOpen}
} >
setIsUnmanagedAttributeOpen(false); {unmanagedAttributePolicies.map((policy) => (
}} <SelectOption key={policy} value={policy}>
selections={userProfileConfig?.unmanagedAttributePolicy} {t(`unmanagedAttributePolicy.${policy}`)}
variant={SelectVariant.single} </SelectOption>
isOpen={isUnmanagedAttributeOpen} ))}
> </Select>
{unmanagedAttributePolicies.map((policy) => ( </FormGroup>
<SelectOption key={policy} value={policy}>
{t(`unmanagedAttributePolicy.${policy}`)}
</SelectOption>
))}
</Select>
</FormGroup>
)}
<FormGroup <FormGroup
label={t("endpoints")} label={t("endpoints")}
labelIcon={ labelIcon={

View file

@ -417,16 +417,13 @@ export const RealmSettingsTabs = ({
</RoutableTabs> </RoutableTabs>
</Tab> </Tab>
)} )}
{isFeatureEnabled(Feature.DeclarativeUserProfile) && <Tab
realm.attributes?.userProfileEnabled === "true" && ( title={<TabTitleText>{t("userProfile")}</TabTitleText>}
<Tab data-testid="rs-user-profile-tab"
title={<TabTitleText>{t("userProfile")}</TabTitleText>} {...userProfileTab}
data-testid="rs-user-profile-tab" >
{...userProfileTab} <UserProfileTab />
> </Tab>
<UserProfileTab />
</Tab>
)}
<Tab <Tab
title={<TabTitleText>{t("userRegistration")}</TabTitleText>} title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
data-testid="rs-userRegistration-tab" data-testid="rs-userRegistration-tab"

View file

@ -15,7 +15,6 @@ import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useFetch } from "../utils/useFetch"; import { useFetch } from "../utils/useFetch";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { UserForm } from "./UserForm"; import { UserForm } from "./UserForm";
import { UserFormFields, toUserRepresentation } from "./form-state"; import { UserFormFields, toUserRepresentation } from "./form-state";
import { toUser } from "./routes/User"; import { toUser } from "./routes/User";
@ -27,7 +26,6 @@ export default function CreateUser() {
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const navigate = useNavigate(); const navigate = useNavigate();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const isFeatureEnabled = useIsFeatureEnabled();
const form = useForm<UserFormFields>({ mode: "onChange" }); const form = useForm<UserFormFields>({ mode: "onChange" });
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]); const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
@ -46,14 +44,7 @@ export default function CreateUser() {
} }
setRealm(realm); setRealm(realm);
setUserProfileMetadata(userProfileMetadata);
const isUserProfileEnabled =
isFeatureEnabled(Feature.DeclarativeUserProfile) &&
realm.attributes?.userProfileEnabled === "true";
setUserProfileMetadata(
isUserProfileEnabled ? userProfileMetadata : undefined,
);
}, },
[], [],
); );

View file

@ -34,7 +34,6 @@ import { useAccess } from "../context/access/Access";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { UserProfileProvider } from "../realm-settings/user-profile/UserProfileContext"; import { UserProfileProvider } from "../realm-settings/user-profile/UserProfileContext";
import { useFetch } from "../utils/useFetch"; import { useFetch } from "../utils/useFetch";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { useParams } from "../utils/useParams"; import { useParams } from "../utils/useParams";
import { UserAttributes } from "./UserAttributes"; import { UserAttributes } from "./UserAttributes";
import { UserConsents } from "./UserConsents"; import { UserConsents } from "./UserConsents";
@ -64,7 +63,6 @@ export default function EditUser() {
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const { id } = useParams<UserParams>(); const { id } = useParams<UserParams>();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const isFeatureEnabled = useIsFeatureEnabled();
const form = useForm<UserFormFields>({ mode: "onChange" }); const form = useForm<UserFormFields>({ mode: "onChange" });
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
const [user, setUser] = useState<UIUserRepresentation>(); const [user, setUser] = useState<UIUserRepresentation>();
@ -113,27 +111,15 @@ export default function EditUser() {
throw new Error(t("notFound")); throw new Error(t("notFound"));
} }
const isUserProfileEnabled =
isFeatureEnabled(Feature.DeclarativeUserProfile) &&
realm.attributes?.userProfileEnabled === "true";
const { userProfileMetadata, ...user } = userData; const { userProfileMetadata, ...user } = userData;
setUserProfileMetadata( setUserProfileMetadata(userProfileMetadata);
isUserProfileEnabled ? userProfileMetadata : undefined, user.unmanagedAttributes = unmanagedAttributes;
user.attributes = filterManagedAttributes(
user.attributes,
unmanagedAttributes,
); );
if (isUserProfileEnabled) { if (upConfig.unmanagedAttributePolicy !== undefined) {
user.unmanagedAttributes = unmanagedAttributes;
user.attributes = filterManagedAttributes(
user.attributes,
unmanagedAttributes,
);
}
if (
upConfig.unmanagedAttributePolicy !== undefined ||
!isUserProfileEnabled
) {
setUnmanagedAttributesEnabled(true); setUnmanagedAttributesEnabled(true);
} }
@ -146,7 +132,7 @@ export default function EditUser() {
setBruteForced({ isBruteForceProtected, isLocked }); setBruteForced({ isBruteForceProtected, isLocked });
form.reset(toUserFormFields(user, isUserProfileEnabled)); form.reset(toUserFormFields(user));
}, },
[refreshCount], [refreshCount],
); );
@ -258,7 +244,7 @@ export default function EditUser() {
]} ]}
onToggle={(value) => onToggle={(value) =>
save({ save({
...toUserFormFields(user, !!userProfileMetadata), ...toUserFormFields(user),
enabled: value, enabled: value,
}) })
} }
@ -295,12 +281,7 @@ export default function EditUser() {
title={<TabTitleText>{t("attributes")}</TabTitleText>} title={<TabTitleText>{t("attributes")}</TabTitleText>}
{...attributesTab} {...attributesTab}
> >
<UserAttributes <UserAttributes user={user} save={save} upConfig={upConfig} />
user={user}
save={save}
upConfig={upConfig}
isUserProfileEnabled={!!userProfileMetadata}
/>
</Tab> </Tab>
)} )}
<Tab <Tab

View file

@ -16,14 +16,12 @@ type UserAttributesProps = {
user: UserRepresentation; user: UserRepresentation;
save: (user: UserFormFields) => void; save: (user: UserFormFields) => void;
upConfig?: UserProfileConfig; upConfig?: UserProfileConfig;
isUserProfileEnabled: boolean;
}; };
export const UserAttributes = ({ export const UserAttributes = ({
user, user,
save, save,
upConfig, upConfig,
isUserProfileEnabled,
}: UserAttributesProps) => { }: UserAttributesProps) => {
const form = useFormContext<UserFormFields>(); const form = useFormContext<UserFormFields>();
@ -36,10 +34,10 @@ export const UserAttributes = ({
reset={() => reset={() =>
form.reset({ form.reset({
...form.getValues(), ...form.getValues(),
attributes: toUserFormFields(user, isUserProfileEnabled).attributes, attributes: toUserFormFields(user).attributes,
}) })
} }
name={isUserProfileEnabled ? "unmanagedAttributes" : "attributes"} name="unmanagedAttributes"
isDisabled={ isDisabled={
UnmanagedAttributePolicy.AdminView == UnmanagedAttributePolicy.AdminView ==
upConfig?.unmanagedAttributePolicy upConfig?.unmanagedAttributePolicy

View file

@ -394,11 +394,7 @@ export const UserForm = ({
<Button <Button
data-testid="cancel-create-user" data-testid="cancel-create-user"
variant="link" variant="link"
onClick={ onClick={user?.id ? () => reset(toUserFormFields(user)) : undefined}
user?.id
? () => reset(toUserFormFields(user, !!userProfileMetadata))
: undefined
}
component={ component={
!user?.id !user?.id
? (props) => ( ? (props) => (

View file

@ -18,18 +18,11 @@ export interface UIUserRepresentation extends UserRepresentation {
unmanagedAttributes?: Record<string, string[]>; unmanagedAttributes?: Record<string, string[]>;
} }
export function toUserFormFields( export function toUserFormFields(data: UIUserRepresentation): UserFormFields {
data: UIUserRepresentation, const attributes: Record<string, string | string[]> = {};
userProfileEnabled: boolean, Object.entries(data.attributes || {}).forEach(
): UserFormFields { ([k, v]) => (attributes[beerify(k)] = v),
let attributes: Record<string, string | string[]> = {}; );
if (userProfileEnabled) {
Object.entries(data.attributes || {}).forEach(
([k, v]) => (attributes[beerify(k)] = v),
);
} else {
attributes = arrayToKeyValue(data.attributes);
}
const unmanagedAttributes = arrayToKeyValue(data.unmanagedAttributes); const unmanagedAttributes = arrayToKeyValue(data.unmanagedAttributes);
return { ...data, attributes, unmanagedAttributes }; return { ...data, attributes, unmanagedAttributes };

View file

@ -3,7 +3,6 @@ import { useServerInfo } from "../context/server-info/ServerInfoProvider";
export enum Feature { export enum Feature {
AdminFineGrainedAuthz = "ADMIN_FINE_GRAINED_AUTHZ", AdminFineGrainedAuthz = "ADMIN_FINE_GRAINED_AUTHZ",
ClientPolicies = "CLIENT_POLICIES", ClientPolicies = "CLIENT_POLICIES",
DeclarativeUserProfile = "DECLARATIVE_USER_PROFILE",
Kerberos = "KERBEROS", Kerberos = "KERBEROS",
DynamicScopes = "DYNAMIC_SCOPES", DynamicScopes = "DYNAMIC_SCOPES",
DPoP = "DPOP", DPoP = "DPOP",

View file

@ -40,7 +40,7 @@ async function startServer() {
[ [
"start-dev", "start-dev",
"--http-port=8180", "--http-port=8180",
"--features=account3,admin-fine-grained-authz,declarative-user-profile,transient-users", "--features=account3,admin-fine-grained-authz,transient-users",
...keycloakArgs, ...keycloakArgs,
], ],
{ {

View file

@ -33,7 +33,7 @@ public class MigrateTo24_0_0 implements Migration {
private static final Logger LOG = Logger.getLogger(MigrateTo24_0_0.class); private static final Logger LOG = Logger.getLogger(MigrateTo24_0_0.class);
public static final ModelVersion VERSION = new ModelVersion("24.0.0"); public static final ModelVersion VERSION = new ModelVersion("24.0.0");
private static final String REALM_USER_PROFILE_ENABLED = "userProfileEnabled"; public static final String REALM_USER_PROFILE_ENABLED = "userProfileEnabled";
@Override @Override
public void migrate(KeycloakSession session) { public void migrate(KeycloakSession session) {
@ -64,15 +64,15 @@ public class MigrateTo24_0_0 implements Migration {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
boolean isUserProfileEnabled = Boolean.parseBoolean(realm.getAttribute(REALM_USER_PROFILE_ENABLED)); boolean isUserProfileEnabled = Boolean.parseBoolean(realm.getAttribute(REALM_USER_PROFILE_ENABLED));
// Remove attribute as user profile is always enabled from this version
realm.removeAttribute(REALM_USER_PROFILE_ENABLED);
if (isUserProfileEnabled) { if (isUserProfileEnabled) {
// existing realms with user profile enabled does not need any addition migration step // existing realms with user profile enabled does not need any addition migration step
LOG.debugf("Skipping migration for realm %s. The declarative user profile is already enabled.", realm.getName()); LOG.debugf("Skipping migration for realm %s. The declarative user profile is already enabled.", realm.getName());
return; return;
} }
// user profile is enabled by default since this version
realm.setAttribute(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
// for backward compatibility in terms of behavior, we enable unmanaged attributes for existing realms // for backward compatibility in terms of behavior, we enable unmanaged attributes for existing realms
// that don't have the declarative user profile enabled // that don't have the declarative user profile enabled
UserProfileProvider provider = session.getProvider(UserProfileProvider.class); UserProfileProvider provider = session.getProvider(UserProfileProvider.class);

View file

@ -28,7 +28,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
@LegacyStore @LegacyStore
public class FeaturesDistTest { public class FeaturesDistTest {
private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: account3:v1, admin-fine-grained-authz:v1, client-secret-rotation:v1, declarative-user-profile:v1, dpop:v1, multi-site:v1, recovery-codes:v1, scripts:v1, token-exchange:v1, update-email:v1"; private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: account3:v1, admin-fine-grained-authz:v1, client-secret-rotation:v1, dpop:v1, multi-site:v1, recovery-codes:v1, scripts:v1, token-exchange:v1, update-email:v1";
@Test @Test
public void testEnableOnBuild(KeycloakDistribution dist) { public void testEnableOnBuild(KeycloakDistribution dist) {
@ -89,7 +89,7 @@ public class FeaturesDistTest {
cliResult.assertStartedDevMode(); cliResult.assertStartedDevMode();
assertThat(cliResult.getOutput(), CoreMatchers.allOf( assertThat(cliResult.getOutput(), CoreMatchers.allOf(
containsString("Preview features enabled: admin-fine-grained-authz:v1, token-exchange:v1"))); containsString("Preview features enabled: admin-fine-grained-authz:v1, token-exchange:v1")));
assertFalse(cliResult.getOutput().contains("declarative-user-profile")); assertFalse(cliResult.getOutput().contains("recovery-codes"));
} }
@Test @Test
@ -100,7 +100,7 @@ public class FeaturesDistTest {
cliResult.assertStartedDevMode(); cliResult.assertStartedDevMode();
assertThat(cliResult.getOutput(), CoreMatchers.allOf( assertThat(cliResult.getOutput(), CoreMatchers.allOf(
containsString("Preview features enabled: admin-fine-grained-authz:v1, token-exchange:v1"))); containsString("Preview features enabled: admin-fine-grained-authz:v1, token-exchange:v1")));
assertFalse(cliResult.getOutput().contains("declarative-user-profile")); assertFalse(cliResult.getOutput().contains("recovery-codes"));
} }
private void assertPreviewFeaturesEnabled(CLIResult result) { private void assertPreviewFeaturesEnabled(CLIResult result) {

View file

@ -48,20 +48,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
HTTP(S): HTTP(S):

View file

@ -48,20 +48,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
HTTP(S): HTTP(S):

View file

@ -59,20 +59,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Config: Config:

View file

@ -59,20 +59,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Config: Config:

View file

@ -59,20 +59,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Config: Config:

View file

@ -59,20 +59,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Config: Config:

View file

@ -75,20 +75,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -75,20 +75,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -75,20 +75,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -75,20 +75,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -76,20 +76,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -76,20 +76,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -76,20 +76,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -76,20 +76,19 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1], --features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], declarative-user-profile[:v1], device-flow[: client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
v1], docker[:v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], impersonation[: dynamic-scopes[:v1], fips[:v1], impersonation[:v1], js-adapter[:v1], kerberos
v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], [:v1], linkedin-oauth[:v1], multi-site[:v1], par[:v1], preview,
par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1],
[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1].
web-authn[:v1].
--features-disabled <feature> --features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api, Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2, account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, authorization, ciba, client-policies, client-secret-rotation, device-flow,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips, docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, par, linkedin-oauth, multi-site, par, preview, recovery-codes, scripts,
preview, recovery-codes, scripts, step-up-authentication, token-exchange, step-up-authentication, token-exchange, transient-users, update-email,
transient-users, update-email, web-authn. web-authn.
Hostname: Hostname:

View file

@ -42,32 +42,27 @@ public class UserResource {
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Map<String, List<String>> getUnmanagedAttributes() { public Map<String, List<String>> getUnmanagedAttributes() {
RealmModel realm = session.getContext().getRealm();
UserProfileProvider provider = session.getProvider(UserProfileProvider.class); UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
if (provider.isEnabled(realm)) { UserProfile profile = provider.create(USER_API, user);
UserProfile profile = provider.create(USER_API, user); Map<String, List<String>> managedAttributes = profile.getAttributes().getReadable();
Map<String, List<String>> managedAttributes = profile.getAttributes().getReadable(); Map<String, List<String>> attributes = new HashMap<>(user.getAttributes());
Map<String, List<String>> attributes = new HashMap<>(user.getAttributes()); UPConfig upConfig = provider.getConfiguration();
UPConfig upConfig = provider.getConfiguration();
if (upConfig.getUnmanagedAttributePolicy() == null) { if (upConfig.getUnmanagedAttributePolicy() == null) {
return Collections.emptyMap(); return Collections.emptyMap();
}
Map<String, List<String>> unmanagedAttributes = profile.getAttributes().getUnmanagedAttributes();
managedAttributes.entrySet().removeAll(unmanagedAttributes.entrySet());
attributes.entrySet().removeAll(managedAttributes.entrySet());
attributes.remove(UserModel.USERNAME);
attributes.remove(UserModel.EMAIL);
return attributes.entrySet().stream()
.filter(entry -> ofNullable(entry.getValue()).orElse(emptyList()).stream().anyMatch(StringUtil::isNotBlank))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
} }
return Collections.emptyMap(); Map<String, List<String>> unmanagedAttributes = profile.getAttributes().getUnmanagedAttributes();
managedAttributes.entrySet().removeAll(unmanagedAttributes.entrySet());
attributes.entrySet().removeAll(managedAttributes.entrySet());
attributes.remove(UserModel.USERNAME);
attributes.remove(UserModel.EMAIL);
return attributes.entrySet().stream()
.filter(entry -> ofNullable(entry.getValue()).orElse(emptyList()).stream().anyMatch(StringUtil::isNotBlank))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
} }
} }

View file

@ -56,7 +56,6 @@ public final class DefaultUserProfile implements UserProfile {
private final Function<Attributes, UserModel> userSupplier; private final Function<Attributes, UserModel> userSupplier;
private final Attributes attributes; private final Attributes attributes;
private final KeycloakSession session; private final KeycloakSession session;
private final boolean isUserProfileEnabled;
private boolean validated; private boolean validated;
private UserModel user; private UserModel user;
@ -67,8 +66,6 @@ public final class DefaultUserProfile implements UserProfile {
this.attributes = attributes; this.attributes = attributes;
this.user = user; this.user = user;
this.session = session; this.session = session;
UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
isUserProfileEnabled = provider.isEnabled(session.getContext().getRealm());
} }
@Override @Override
@ -226,7 +223,7 @@ public final class DefaultUserProfile implements UserProfile {
continue; continue;
} }
boolean isUnmanagedAttribute = isUserProfileEnabled && metadata.getAttribute(name).isEmpty(); boolean isUnmanagedAttribute = metadata.getAttribute(name).isEmpty();
String value = isUnmanagedAttribute ? null : values.stream().findFirst().orElse(null); String value = isUnmanagedAttribute ? null : values.stream().findFirst().orElse(null);
if (UserModel.USERNAME.equals(name)) { if (UserModel.USERNAME.equals(name)) {

View file

@ -19,7 +19,6 @@ package org.keycloak.userprofile;
import java.util.Map; import java.util.Map;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.representations.userprofile.config.UPConfig;
@ -88,12 +87,4 @@ public interface UserProfileProvider extends Provider {
* @see #getConfiguration() * @see #getConfiguration()
*/ */
void setConfiguration(UPConfig configuration); void setConfiguration(UPConfig configuration);
/**
* Returns whether the declarative provider is enabled to a realm
*
* @deprecated should be removed once {@link DeclarativeUserProfileProvider} becomes the default.
* @return {@code true} if the declarative provider is enabled. Otherwise, {@code false}.
*/
boolean isEnabled(RealmModel realm);
} }

View file

@ -45,6 +45,7 @@ import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -175,7 +176,12 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
} else if (variable.startsWith("CLAIM.")) { } else if (variable.startsWith("CLAIM.")) {
String name = variable.substring("CLAIM.".length()); String name = variable.substring("CLAIM.".length());
Object value = AbstractClaimMapper.getClaimValue(context, name); Object value = AbstractClaimMapper.getClaimValue(context, name);
if (value == null) value = ""; if (value == null) {
value = "";
} else if (value instanceof Collection && ((Collection<?>) value).size() == 1) {
// In case the value is list with single value, it might be preferred to avoid converting whole collection toString, but rather use value like "foo" instead of "[foo]"
value = ((Collection<?>) value).iterator().next();
}
m.appendReplacement(sb, transformer.apply(value.toString())); m.appendReplacement(sb, transformer.apply(value.toString()));
} else { } else {
m.appendReplacement(sb, m.group(1)); m.appendReplacement(sb, m.group(1));

View file

@ -385,12 +385,6 @@ public class RealmAdminResource {
if (auth.users().canView()) { if (auth.users().canView()) {
rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername()); rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
if (realm.getAttribute(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED, Boolean.FALSE)) {
// add the user profile attribute if enabled
Map<String, String> attrs = Optional.ofNullable(rep.getAttributes()).orElse(new HashMap<>());
attrs.put(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
rep.setAttributes(attrs);
}
} }
if (auth.realm().canViewIdentityProviders()) { if (auth.realm().canViewIdentityProviders()) {

View file

@ -41,7 +41,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.userprofile.config.DeclarativeUserProfileModel; import org.keycloak.userprofile.config.DeclarativeUserProfileModel;
import org.keycloak.representations.userprofile.config.UPAttribute; import org.keycloak.representations.userprofile.config.UPAttribute;
@ -52,7 +51,6 @@ import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.userprofile.config.UPConfigUtils; import org.keycloak.userprofile.config.UPConfigUtils;
import org.keycloak.representations.userprofile.config.UPGroup; import org.keycloak.representations.userprofile.config.UPGroup;
import org.keycloak.userprofile.validator.AttributeRequiredByMetadataValidator; import org.keycloak.userprofile.validator.AttributeRequiredByMetadataValidator;
import org.keycloak.userprofile.validator.BlankAttributeValidator;
import org.keycloak.userprofile.validator.ImmutableAttributeValidator; import org.keycloak.userprofile.validator.ImmutableAttributeValidator;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.validate.AbstractSimpleValidator; import org.keycloak.validate.AbstractSimpleValidator;
@ -68,7 +66,6 @@ import org.keycloak.validate.ValidatorConfig;
public class DeclarativeUserProfileProvider implements UserProfileProvider { public class DeclarativeUserProfileProvider implements UserProfileProvider {
public static final String UP_COMPONENT_CONFIG_KEY = "kc.user.profile.config"; public static final String UP_COMPONENT_CONFIG_KEY = "kc.user.profile.config";
public static final String REALM_USER_PROFILE_ENABLED = "userProfileEnabled";
protected static final String PARSED_CONFIG_COMPONENT_KEY = "kc.user.profile.metadata"; protected static final String PARSED_CONFIG_COMPONENT_KEY = "kc.user.profile.metadata";
protected static final String PARSED_UP_CONFIG_COMPONENT_KEY = "kc.parsed.up.config"; protected static final String PARSED_UP_CONFIG_COMPONENT_KEY = "kc.parsed.up.config";
@ -94,7 +91,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
} }
private final KeycloakSession session; private final KeycloakSession session;
private final boolean isDeclarativeConfigurationEnabled;
private final String providerId; private final String providerId;
private final Map<UserProfileContext, UserProfileMetadata> contextualMetadataRegistry; private final Map<UserProfileContext, UserProfileMetadata> contextualMetadataRegistry;
protected final UPConfig parsedDefaultRawConfig; protected final UPConfig parsedDefaultRawConfig;
@ -102,23 +98,17 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
public DeclarativeUserProfileProvider(KeycloakSession session, DeclarativeUserProfileProviderFactory factory) { public DeclarativeUserProfileProvider(KeycloakSession session, DeclarativeUserProfileProviderFactory factory) {
this.session = session; this.session = session;
this.providerId = factory.getId(); this.providerId = factory.getId();
this.isDeclarativeConfigurationEnabled = factory.isDeclarativeConfigurationEnabled();
this.contextualMetadataRegistry = factory.getContextualMetadataRegistry(); this.contextualMetadataRegistry = factory.getContextualMetadataRegistry();
this.parsedDefaultRawConfig = factory.getParsedDefaultRawConfig(); this.parsedDefaultRawConfig = factory.getParsedDefaultRawConfig();
} }
protected Attributes createAttributes(UserProfileContext context, Map<String, ?> attributes, protected Attributes createAttributes(UserProfileContext context, Map<String, ?> attributes,
UserModel user, UserProfileMetadata metadata) { UserModel user, UserProfileMetadata metadata) {
RealmModel realm = session.getContext().getRealm();
if (isEnabled(realm)) { if (user != null && user.getServiceAccountClientLink() != null) {
if (user != null && user.getServiceAccountClientLink() != null) { return new LegacyAttributes(context, attributes, user, metadata, session);
return new LegacyAttributes(context, attributes, user, metadata, session);
}
return new DefaultAttributes(context, attributes, user, metadata, session);
} }
return new DefaultAttributes(context, attributes, user, metadata, session);
return new LegacyAttributes(context, attributes, user, metadata, session);
} }
@Override @Override
@ -187,16 +177,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
UserProfileMetadata decoratedMetadata = metadata.clone(); UserProfileMetadata decoratedMetadata = metadata.clone();
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
if (!isEnabled(realm)) {
if(!context.equals(UserProfileContext.USER_API)
&& !context.equals(UserProfileContext.UPDATE_EMAIL)) {
decoratedMetadata.addAttribute(UserModel.FIRST_NAME, 1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(
Messages.MISSING_FIRST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${firstName}");
decoratedMetadata.addAttribute(UserModel.LAST_NAME, 2, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${lastName}");
}
return decoratedMetadata;
}
ComponentModel component = getComponentModel().orElse(null); ComponentModel component = getComponentModel().orElse(null);
if (component == null) { if (component == null) {
@ -218,12 +198,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
@Override @Override
public UPConfig getConfiguration() { public UPConfig getConfiguration() {
RealmModel realm = session.getContext().getRealm();
if (!isEnabled(realm)) {
return parsedDefaultRawConfig.clone();
}
Optional<ComponentModel> component = getComponentModel(); Optional<ComponentModel> component = getComponentModel();
if (component.isPresent()) { if (component.isPresent()) {
@ -522,11 +496,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
model.getConfig().remove(UP_COMPONENT_CONFIG_KEY); model.getConfig().remove(UP_COMPONENT_CONFIG_KEY);
} }
@Override
public boolean isEnabled(RealmModel realm) {
return isDeclarativeConfigurationEnabled && realm.getAttribute(REALM_USER_PROFILE_ENABLED, false);
}
@Override @Override
public void close() { public void close() {
} }

View file

@ -85,8 +85,6 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
private static final Pattern readOnlyAttributesPattern = getRegexPatternString(DEFAULT_READ_ONLY_ATTRIBUTES); private static final Pattern readOnlyAttributesPattern = getRegexPatternString(DEFAULT_READ_ONLY_ATTRIBUTES);
private static final Pattern adminReadOnlyAttributesPattern = getRegexPatternString(DEFAULT_ADMIN_READ_ONLY_ATTRIBUTES); private static final Pattern adminReadOnlyAttributesPattern = getRegexPatternString(DEFAULT_ADMIN_READ_ONLY_ATTRIBUTES);
private boolean isDeclarativeConfigurationEnabled;
private UPConfig parsedDefaultRawConfig; private UPConfig parsedDefaultRawConfig;
private final Map<UserProfileContext, UserProfileMetadata> contextualMetadataRegistry = new HashMap<>(); private final Map<UserProfileContext, UserProfileMetadata> contextualMetadataRegistry = new HashMap<>();
@ -198,7 +196,6 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
isDeclarativeConfigurationEnabled = Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_USER_PROFILE);
parsedDefaultRawConfig = UPConfigUtils.parseDefaultConfig(); parsedDefaultRawConfig = UPConfigUtils.parseDefaultConfig();
// make sure registry is clear in case of re-deploy // make sure registry is clear in case of re-deploy
@ -313,12 +310,8 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
* @return the metadata * @return the metadata
*/ */
protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata) { protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata) {
if (isDeclarativeConfigurationEnabled) { // default metadata for each context is based on the default realm configuration
// default metadata for each context is based on the default realm configuration return new DeclarativeUserProfileProvider(null, this).decorateUserProfileForCache(metadata, parsedDefaultRawConfig);
return new DeclarativeUserProfileProvider(null, this).decorateUserProfileForCache(metadata, parsedDefaultRawConfig);
}
return metadata;
} }
private AttributeValidatorMetadata createReadOnlyAttributeUnchangedValidator(Pattern pattern) { private AttributeValidatorMetadata createReadOnlyAttributeUnchangedValidator(Pattern pattern) {
@ -461,10 +454,6 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
// GETTER METHODS FOR INTERNAL FIELDS // GETTER METHODS FOR INTERNAL FIELDS
protected boolean isDeclarativeConfigurationEnabled() {
return isDeclarativeConfigurationEnabled;
}
protected UPConfig getParsedDefaultRawConfig() { protected UPConfig getParsedDefaultRawConfig() {
return parsedDefaultRawConfig; return parsedDefaultRawConfig;
} }

View file

@ -524,7 +524,7 @@ so please make sure you rebuild all `testsuite/integration-arquillian` child mod
## Cluster tests ## Cluster tests
Cluster tests use 2 backend servers (Keycloak on Wildfly/EAP or Keycloak on Undertow), 1 frontend loadbalancer server node and one shared DB. Invalidation tests don't use loadbalancer. Cluster tests use 2 backend servers (Keycloak on Quarkus or Keycloak on Undertow), 1 frontend loadbalancer server node and one shared DB. Invalidation tests don't use loadbalancer.
The browser usually communicates directly with the backend node1 and after doing some change here (eg. updating user), it verifies that the change is visible on node2 and user is updated here as well. The browser usually communicates directly with the backend node1 and after doing some change here (eg. updating user), it verifies that the change is visible on node2 and user is updated here as well.
Failover tests use loadbalancer and they require the setup with the distributed infinispan caches switched to have 2 owners (default value is 1 owner). Otherwise failover won't reliably work. Failover tests use loadbalancer and they require the setup with the distributed infinispan caches switched to have 2 owners (default value is 1 owner). Otherwise failover won't reliably work.

View file

@ -19,20 +19,28 @@
package org.keycloak.testsuite.account; package org.keycloak.testsuite.account;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.representations.account.UserRepresentation; import org.keycloak.representations.account.UserRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.userprofile.UserProfileConstants;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -47,6 +55,46 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
private static final Logger logger = Logger.getLogger(AccountRestServiceReadOnlyAttributesTest.class); private static final Logger logger = Logger.getLogger(AccountRestServiceReadOnlyAttributesTest.class);
@Before
public void configureUserProfile() {
UserProfileResource userProfileRes = testRealm().users().userProfile();
UPConfig cfg = userProfileRes.getConfiguration();
//cfg.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
cfg.addOrReplaceAttribute(createUpAttribute("someOtherAttr"));
cfg.addOrReplaceAttribute(createUpAttribute("usercertificate"));
cfg.addOrReplaceAttribute(createUpAttribute("uSErCertificate"));
cfg.addOrReplaceAttribute(createUpAttribute("KERBEROS_PRINCIPAL"));
cfg.addOrReplaceAttribute(createUpAttribute("noKerberos_Principal"));
cfg.addOrReplaceAttribute(createUpAttribute("KERBEROS_PRINCIPALno"));
cfg.addOrReplaceAttribute(createUpAttribute("enabled"));
cfg.addOrReplaceAttribute(createUpAttribute("CREATED_TIMESTAMP"));
cfg.addOrReplaceAttribute(createUpAttribute("saml.something"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedfoo"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedFOo"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedFoot"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedbar"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedBAr"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedBArr"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedbarrier"));
cfg.addOrReplaceAttribute(createUpAttribute("nodeniedbar"));
cfg.addOrReplaceAttribute(createUpAttribute("nodeniedBARrier"));
cfg.addOrReplaceAttribute(createUpAttribute("saml.persistent.name.id.for.foo"));
cfg.addOrReplaceAttribute(createUpAttribute("saml.persistent.name.id.for._foo_"));
cfg.addOrReplaceAttribute(createUpAttribute("saml.persistent.name.idafor.foo"));
// TODO: Doublecheck this. We should either document that attributes with custom characters are not allowed or we should enable to configure them
// cfg.addOrReplaceAttribute(createUpAttribute("deniedsome/thing"));
// cfg.addOrReplaceAttribute(createUpAttribute("deniedsome*thing"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedsomeithing"));
cfg.addOrReplaceAttribute(createUpAttribute("deniedSomeAdmin"));
userProfileRes.update(cfg);
}
private UPAttribute createUpAttribute(String name) {
return new UPAttribute(name, new UPAttributePermissions(Collections.emptySet(), Set.of(UserProfileConstants.ROLE_USER, UserProfileConstants.ROLE_ADMIN)));
}
// Test read-only attributes from provider configuration have precedence over the user-profile realm configuration settings (Read-only attributes from provider config are always read-only)
@Test @Test
public void testUpdateProfileCannotUpdateReadOnlyAttributes() throws IOException { public void testUpdateProfileCannotUpdateReadOnlyAttributes() throws IOException {
// Denied by default // Denied by default
@ -85,9 +133,10 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
testAccountUpdateAttributeExpectFailure("saml.persistent.name.id.for._foo_"); testAccountUpdateAttributeExpectFailure("saml.persistent.name.id.for._foo_");
testAccountUpdateAttributeExpectSuccess("saml.persistent.name.idafor.foo"); testAccountUpdateAttributeExpectSuccess("saml.persistent.name.idafor.foo");
// TODO: Uncomment similarly like above
// Special characters inside should be quoted // Special characters inside should be quoted
testAccountUpdateAttributeExpectFailure("deniedsome/thing"); //testAccountUpdateAttributeExpectFailure("deniedsome/thing");
testAccountUpdateAttributeExpectFailure("deniedsome*thing"); //testAccountUpdateAttributeExpectFailure("deniedsome*thing");
testAccountUpdateAttributeExpectSuccess("deniedsomeithing"); testAccountUpdateAttributeExpectSuccess("deniedsomeithing");
// Denied only for admin, but allowed for normal user // Denied only for admin, but allowed for normal user
@ -135,9 +184,10 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
user.singleAttribute(attrName, "foo-updated"); user.singleAttribute(attrName, "foo-updated");
updateError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED); updateError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Ignore removal of read-only attributes // Removal of read-only attribute not allowed
user.getAttributes().remove(attrName); user.getAttributes().remove(attrName);
user = updateAndGet(user); updateError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
user = get();
assertTrue(user.getAttributes().containsKey(attrName)); assertTrue(user.getAttributes().containsKey(attrName));
// Revert with admin REST // Revert with admin REST
@ -178,6 +228,10 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
private UserRepresentation updateAndGet(UserRepresentation user) throws IOException { private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus(); int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
assertEquals(204, status); assertEquals(204, status);
return get();
}
private UserRepresentation get() throws IOException {
return SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class); return SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
} }

View file

@ -17,8 +17,10 @@
package org.keycloak.testsuite.account; package org.keycloak.testsuite.account;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.http.impl.client.CloseableHttpClient;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
@ -66,11 +68,11 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest; import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.TokenUtil; import org.keycloak.testsuite.util.TokenUtil;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.validate.validators.EmailValidator;
import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
@ -91,6 +93,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -100,6 +103,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@Override
@Before
public void before() {
super.before();
setUserProfileConfiguration(null);
}
@Test @Test
public void testEditUsernameAllowed() throws IOException { public void testEditUsernameAllowed() throws IOException {
UserRepresentation user = getUser(); UserRepresentation user = getUser();
@ -115,14 +125,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(true); realmRep.setEditUsernameAllowed(true);
realm.update(realmRep); realm.update(realmRep);
user = getUser(); user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata()); assertNotNull(user.getUserProfileMetadata());
// can write both username and email // can write both username and email
assertUserProfileAttributeMetadata(user, "username", "${username}", true, false); assertUserProfileAttributeMetadata(user, "username", "${username}", true, false);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false); assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
assertUserProfileAttributeMetadata(user, "firstName", "${firstName}", true, false); assertUserProfileAttributeMetadata(user, "firstName", "${firstName}", true, false);
assertUserProfileAttributeMetadata(user, "lastName", "${lastName}", true, false); assertUserProfileAttributeMetadata(user, "lastName", "${lastName}", true, false);
}
user.setUsername("changed-username"); user.setUsername("changed-username");
user.setEmail("changed-email@keycloak.org"); user.setEmail("changed-email@keycloak.org");
user = updateAndGet(user); user = updateAndGet(user);
@ -133,12 +143,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(false); realmRep.setEditUsernameAllowed(false);
realm.update(realmRep); realm.update(realmRep);
user = getUser(); user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata()); assertNotNull(user.getUserProfileMetadata());
// username is readonly but email is writable // username is readonly but email is writable
assertUserProfileAttributeMetadata(user, "username", "${username}", true, true); assertUserProfileAttributeMetadata(user, "username", "${username}", true, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false); assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
}
user.setUsername("should-not-change"); user.setUsername("should-not-change");
user.setEmail("changed-email@keycloak.org"); user.setEmail("changed-email@keycloak.org");
updateError(user, 400, Messages.READ_ONLY_USERNAME); updateError(user, 400, Messages.READ_ONLY_USERNAME);
@ -147,13 +157,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(true); realmRep.setEditUsernameAllowed(true);
realm.update(realmRep); realm.update(realmRep);
user = getUser(); user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata()); assertNotNull(user.getUserProfileMetadata());
// username is read-only, not required, and is the same as email // username is read-only, not required, and is the same as email
// but email is writable // but email is writable
assertUserProfileAttributeMetadata(user, "username", "${username}", false, true); assertUserProfileAttributeMetadata(user, "username", "${username}", false, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false); assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
}
user.setUsername("should-be-the-email"); user.setUsername("should-be-the-email");
user.setEmail("user@keycloak.org"); user.setEmail("user@keycloak.org");
user = updateAndGet(user); user = updateAndGet(user);
@ -164,12 +174,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(false); realmRep.setEditUsernameAllowed(false);
realm.update(realmRep); realm.update(realmRep);
user = getUser(); user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata()); assertNotNull(user.getUserProfileMetadata());
// username is read-only and is the same as email, but email is read-only // username is read-only and is the same as email, but email is read-only
assertUserProfileAttributeMetadata(user, "username", "${username}", false, true); assertUserProfileAttributeMetadata(user, "username", "${username}", false, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, true); assertUserProfileAttributeMetadata(user, "email", "${email}", true, true);
}
user.setUsername("should-be-the-email"); user.setUsername("should-be-the-email");
user.setEmail("should-not-change@keycloak.org"); user.setEmail("should-not-change@keycloak.org");
user = updateAndGet(user); user = updateAndGet(user);
@ -210,45 +220,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
assertNull(user.getUserProfileMetadata()); assertNull(user.getUserProfileMetadata());
} }
@Test protected static UserProfileAttributeMetadata getUserProfileAttributeMetadata(UserRepresentation user, String attName) {
public void testEditUsernameDisallowed() throws IOException {
try {
RealmResource realm = adminClient.realm("test");
RealmRepresentation realmRep = realm.toRepresentation();
realmRep.setEditUsernameAllowed(false);
realm.update(realmRep);
UserRepresentation user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata());
UserProfileAttributeMetadata upm = assertUserProfileAttributeMetadata(user, "username", "${username}", true, true);
//makes sure internal validators are not exposed
Assert.assertEquals(0, upm.getValidators().size());
upm = assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
Assert.assertEquals(1, upm.getValidators().size());
Assert.assertTrue(upm.getValidators().containsKey(EmailValidator.ID));
}
realmRep.setRegistrationEmailAsUsername(true);
realm.update(realmRep);
user = getUser();
if (isDeclarativeUserProfile()) {
UserProfileAttributeMetadata upm = assertUserProfileAttributeMetadata(user, "email", "${email}", true, true);
Assert.assertEquals(1, upm.getValidators().size());
Assert.assertTrue(upm.getValidators().containsKey(EmailValidator.ID));
assertUserProfileAttributeMetadata(user, "firstName", "${firstName}", true, false);
assertUserProfileAttributeMetadata(user, "lastName", "${lastName}", true, false);
}
} finally {
RealmRepresentation realmRep = testRealm().toRepresentation();
realmRep.setEditUsernameAllowed(true);
testRealm().update(realmRep);
}
}
protected UserProfileAttributeMetadata getUserProfileAttributeMetadata(UserRepresentation user, String attName) {
if(user.getUserProfileMetadata() == null) if(user.getUserProfileMetadata() == null)
return null; return null;
for(UserProfileAttributeMetadata uam : user.getUserProfileMetadata().getAttributes()) { for(UserProfileAttributeMetadata uam : user.getUserProfileMetadata().getAttributes()) {
@ -259,14 +231,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
return null; return null;
} }
protected UserProfileAttributeMetadata assertUserProfileAttributeMetadata(UserRepresentation user, String attName, String displayName, boolean required, boolean readOnly) { protected static UserProfileAttributeMetadata assertUserProfileAttributeMetadata(UserRepresentation user, String attName, String displayName, boolean required, boolean readOnly) {
UserProfileAttributeMetadata uam = getUserProfileAttributeMetadata(user, attName); UserProfileAttributeMetadata uam = getUserProfileAttributeMetadata(user, attName);
if (isDeclarativeUserProfile()) {
assertNotNull(uam); assertNotNull(uam);
assertEquals("Unexpected display name for attribute " + uam.getName(), displayName, uam.getDisplayName()); assertEquals("Unexpected display name for attribute " + uam.getName(), displayName, uam.getDisplayName());
assertEquals("Unexpected required flag for attribute " + uam.getName(), required, uam.isRequired()); assertEquals("Unexpected required flag for attribute " + uam.getName(), required, uam.isRequired());
assertEquals("Unexpected readonly flag for attribute " + uam.getName(), readOnly, uam.isReadOnly()); assertEquals("Unexpected readonly flag for attribute " + uam.getName(), readOnly, uam.isReadOnly());
}
return uam; return uam;
} }
@ -284,6 +256,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test @Test
public void testUpdateSingleField() throws IOException { public void testUpdateSingleField() throws IOException {
String userProfileConfig = "{\"attributes\": ["
+ "{\"name\": \"email\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}}"
+ "]}";
setUserProfileConfiguration(userProfileConfig);
UserRepresentation user = getUser(); UserRepresentation user = getUser();
String originalUsername = user.getUsername(); String originalUsername = user.getUsername();
String originalFirstName = user.getFirstName(); String originalFirstName = user.getFirstName();
@ -374,6 +353,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test @Test
public void testUpdateProfileEvent() throws IOException { public void testUpdateProfileEvent() throws IOException {
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"attr1\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"attr2\"," + PERMISSIONS_ALL + "}"
+ "]}");
UserRepresentation user = getUser(); UserRepresentation user = getUser();
String originalUsername = user.getUsername(); String originalUsername = user.getUsername();
String originalFirstName = user.getFirstName(); String originalFirstName = user.getFirstName();
@ -426,6 +412,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test @Test
public void testUpdateProfile() throws IOException { public void testUpdateProfile() throws IOException {
String userProfileCfg = "{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"attr1\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"attr2\"," + PERMISSIONS_ALL + "}"
+ "]}";
setUserProfileConfiguration(userProfileCfg);
UserRepresentation user = getUser(); UserRepresentation user = getUser();
String originalUsername = user.getUsername(); String originalUsername = user.getUsername();
String originalFirstName = user.getFirstName(); String originalFirstName = user.getFirstName();
@ -602,6 +596,10 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
protected UserRepresentation getUser(boolean fetchMetadata) throws IOException { protected UserRepresentation getUser(boolean fetchMetadata) throws IOException {
String accountUrl = getAccountUrl(null) + "?userProfileMetadata=" + fetchMetadata; String accountUrl = getAccountUrl(null) + "?userProfileMetadata=" + fetchMetadata;
return getUser(accountUrl, httpClient, tokenUtil);
}
protected static UserRepresentation getUser(String accountUrl, CloseableHttpClient httpClient, TokenUtil tokenUtil) throws IOException {
SimpleHttp a = SimpleHttp.doGet(accountUrl, httpClient).auth(tokenUtil.getToken()); SimpleHttp a = SimpleHttp.doGet(accountUrl, httpClient).auth(tokenUtil.getToken());
try { try {
@ -1719,7 +1717,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
} }
} }
protected boolean isDeclarativeUserProfile() { protected void setUserProfileConfiguration(String configuration) {
return false; VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
} }
} }

View file

@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.keycloak.testsuite.account.AccountRestServiceTest.assertUserProfileAttributeMetadata;
import static org.keycloak.testsuite.account.AccountRestServiceTest.getUserProfileAttributeMetadata;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL; import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE; import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_ONLY; import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_ONLY;
@ -34,7 +36,6 @@ import java.util.Optional;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
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.models.UserModel; import org.keycloak.models.UserModel;
@ -42,31 +43,24 @@ import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserProfileMetadata; import org.keycloak.representations.idm.UserProfileMetadata;
import org.keycloak.representations.account.UserRepresentation; import org.keycloak.representations.account.UserRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest; import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
/** /**
* Test account rest service with custom user profile configurations
* *
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
* *
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE) public class AccountRestServiceWithUserProfileTest extends AbstractRestServiceTest {
public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTest {
@Override @Override
@Before @Before
public void before() { public void before() {
super.before(); super.before();
enableDynamicUserProfile();
setUserProfileConfiguration(null); setUserProfileConfiguration(null);
} }
@Override
protected boolean isDeclarativeUserProfile() {
return true;
}
private final static String UP_CONFIG_FOR_METADATA = "{\"attributes\": [" private final static String UP_CONFIG_FOR_METADATA = "{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {\"scopes\":[\"profile\"]}, \"displayName\": \"${profile.firstName}\", \"validations\": {\"length\": { \"max\": 255 }}}," + "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {\"scopes\":[\"profile\"]}, \"displayName\": \"${profile.firstName}\", \"validations\": {\"length\": { \"max\": 255 }}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}, \"displayName\": \"Last name\", \"annotations\": {\"formHintKey\" : \"userEmailFormFieldHint\", \"anotherKey\" : 10, \"yetAnotherKey\" : \"some value\"}}," + "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}, \"displayName\": \"Last name\", \"annotations\": {\"formHintKey\" : \"userEmailFormFieldHint\", \"anotherKey\" : 10, \"yetAnotherKey\" : \"some value\"}},"
@ -101,9 +95,7 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
@Test @Test
@Override
public void testEditUsernameAllowed() throws IOException { public void testEditUsernameAllowed() throws IOException {
super.testEditUsernameAllowed();
setUserProfileConfiguration(UP_CONFIG_FOR_METADATA); setUserProfileConfiguration(UP_CONFIG_FOR_METADATA);
UserRepresentation user = getUser(); UserRepresentation user = getUser();
@ -221,7 +213,6 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
@Test @Test
@Override
public void testEditUsernameDisallowed() throws IOException { public void testEditUsernameDisallowed() throws IOException {
try { try {
@ -340,40 +331,6 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
} }
} }
@Test
public void testUpdateProfileEvent() throws IOException {
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"attr1\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"attr2\"," + PERMISSIONS_ALL + "}"
+ "]}");
super.testUpdateProfileEvent();
}
@Test
@Override
public void testUpdateProfile() throws IOException {
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"attr1\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"attr2\"," + PERMISSIONS_ALL + "}"
+ "]}");
super.testUpdateProfile();
}
@Test
@Override
public void testUpdateSingleField() throws IOException {
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"email\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}}"
+ "]}");
super.testUpdateSingleField();
}
@Test @Test
public void testManageUserLocaleAttribute() throws IOException { public void testManageUserLocaleAttribute() throws IOException {
RealmRepresentation realmRep = testRealm().toRepresentation(); RealmRepresentation realmRep = testRealm().toRepresentation();
@ -417,12 +374,24 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration); VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
} }
protected void enableDynamicUserProfile() { protected UserRepresentation getUser() throws IOException {
RealmRepresentation testRealm = testRealm().toRepresentation(); return getUser(true);
}
VerifyProfileTest.enableDynamicUserProfile(testRealm); protected UserRepresentation getUser(boolean fetchMetadata) throws IOException {
String accountUrl = getAccountUrl(null) + "?userProfileMetadata=" + fetchMetadata;
return AccountRestServiceTest.getUser(accountUrl, httpClient, tokenUtil);
}
testRealm().update(testRealm); protected UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
SimpleHttp a = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user);
try {
assertEquals(204, a.asStatus());
} catch (AssertionError e) {
System.err.println("Error during user update: " + a.asString());
throw e;
}
return getUser();
} }
} }

View file

@ -17,7 +17,9 @@
package org.keycloak.testsuite.account.custom; package org.keycloak.testsuite.account.custom;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.models.AuthenticationExecutionModel.Requirement; import org.keycloak.models.AuthenticationExecutionModel.Requirement;
import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.models.utils.TimeBasedOTP;
@ -29,6 +31,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.admin.Users; import org.keycloak.testsuite.admin.Users;
import org.keycloak.testsuite.auth.page.login.OneTimeCode; import org.keycloak.testsuite.auth.page.login.OneTimeCode;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.LoginConfigTotpPage; import org.keycloak.testsuite.pages.LoginConfigTotpPage;
import org.keycloak.testsuite.pages.LoginTotpPage; import org.keycloak.testsuite.pages.LoginTotpPage;
import org.keycloak.testsuite.pages.PageUtils; import org.keycloak.testsuite.pages.PageUtils;
@ -86,6 +89,12 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
testLoginOneTimeCodePage.setAuthRealm(testRealmPage); testLoginOneTimeCodePage.setAuthRealm(testRealmPage);
} }
@Before
public void configureUserProfile() {
UserProfileResource userProfileRes = testRealmResource().users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
}
private void configureRequiredActions() { private void configureRequiredActions() {
//set configure TOTP as required action to test user //set configure TOTP as required action to test user
List<String> requiredActions = new ArrayList<>(); List<String> requiredActions = new ArrayList<>();

View file

@ -16,13 +16,25 @@
*/ */
package org.keycloak.testsuite.actions; package org.keycloak.testsuite.actions;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.userprofile.UserProfileConstants.ROLE_USER;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
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.validate.validators.LengthValidator;
public class AppInitiatedActionUpdateEmailTest extends AbstractAppInitiatedActionUpdateEmailTest { public class AppInitiatedActionUpdateEmailTest extends AbstractAppInitiatedActionUpdateEmailTest {
@ -41,6 +53,46 @@ public class AppInitiatedActionUpdateEmailTest extends AbstractAppInitiatedActio
Assert.assertEquals("Brady", user.getLastName()); Assert.assertEquals("Brady", user.getLastName());
} }
@Test
public void testCustomEmailValidator() throws Exception {
UserProfileResource userProfile = testRealm().users().userProfile();
UPConfig upConfig = userProfile.getConfiguration();
UPAttribute emailConfig = upConfig.getAttribute(UserModel.EMAIL);
emailConfig.addValidation(LengthValidator.ID, Map.of("min", "1", "max", "1"));
getCleanup().addCleanup(() -> {
emailConfig.getValidations().remove(LengthValidator.ID);
userProfile.update(upConfig);
});
userProfile.update(upConfig);
changeEmailUsingAIA("new@email.com");
assertTrue(emailUpdatePage.getEmailError().contains("Length must be between 1 and 1."));
emailConfig.getValidations().remove(LengthValidator.ID);
userProfile.update(upConfig);
changeEmailUsingAIA("new@email.com");
events.expect(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
}
@Test
public void testOnlyEmailSupportedInContext() throws Exception {
UserProfileResource userProfile = testRealm().users().userProfile();
UPConfig upConfig = userProfile.getConfiguration();
String unexpectedAttributeName = "unexpectedAttribute";
upConfig.addOrReplaceAttribute(new UPAttribute(unexpectedAttributeName, new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_USER), Set.of())));
getCleanup().addCleanup(() -> {
upConfig.removeAttribute(unexpectedAttributeName);
userProfile.update(upConfig);
});
userProfile.update(upConfig);
assertFalse(driver.getPageSource().contains(unexpectedAttributeName));
changeEmailUsingAIA("new@email.com");
events.expect(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
}
@Override @Override
protected void changeEmailUsingAIA(String newEmail) throws Exception { protected void changeEmailUsingAIA(String newEmail) throws Exception {
doAIA(); doAIA();

View file

@ -1,89 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.actions;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.userprofile.UserProfileConstants.ROLE_USER;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.common.Profile;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
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.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.validate.validators.LengthValidator;
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public class AppInitiatedActionUpdateEmailUserProfileTest extends AppInitiatedActionUpdateEmailTest {
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
}
@Test
public void testCustomEmailValidator() throws Exception {
UserProfileResource userProfile = testRealm().users().userProfile();
UPConfig upConfig = userProfile.getConfiguration();
UPAttribute emailConfig = upConfig.getAttribute(UserModel.EMAIL);
emailConfig.addValidation(LengthValidator.ID, Map.of("min", "1", "max", "1"));
getCleanup().addCleanup(() -> {
emailConfig.getValidations().remove(LengthValidator.ID);
userProfile.update(upConfig);
});
userProfile.update(upConfig);
changeEmailUsingAIA("new@email.com");
assertTrue(emailUpdatePage.getEmailError().contains("Length must be between 1 and 1."));
emailConfig.getValidations().remove(LengthValidator.ID);
userProfile.update(upConfig);
changeEmailUsingAIA("new@email.com");
events.expect(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
}
@Test
public void testOnlyEmailSupportedInContext() throws Exception {
UserProfileResource userProfile = testRealm().users().userProfile();
UPConfig upConfig = userProfile.getConfiguration();
String unexpectedAttributeName = "unexpectedAttribute";
upConfig.addOrReplaceAttribute(new UPAttribute(unexpectedAttributeName, new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_USER), Set.of())));
getCleanup().addCleanup(() -> {
upConfig.removeAttribute(unexpectedAttributeName);
userProfile.update(upConfig);
});
userProfile.update(upConfig);
assertFalse(driver.getPageSource().contains(unexpectedAttributeName));
changeEmailUsingAIA("new@email.com");
events.expect(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
}
}

View file

@ -37,6 +37,8 @@ import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
/** /**
* Only covers basic use cases for App Initialized actions. Complete dynamic user profile behavior is tested in {@link RequiredActionUpdateProfileWithUserProfileTest} as it shares same code as the App initialized action.
*
* @author Stan Silvert * @author Stan Silvert
*/ */
public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedActionTest { public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedActionTest {
@ -52,10 +54,6 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
@Page @Page
protected ErrorPage errorPage; protected ErrorPage errorPage;
protected boolean isDynamicForm() {
return false;
}
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
} }
@ -212,10 +210,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
Assert.assertEquals("New last", updateProfilePage.getLastName()); Assert.assertEquals("New last", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail()); Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm()) Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
else
Assert.assertEquals("Please specify first name.", updateProfilePage.getInputErrors().getFirstNameError());
events.assertEmpty(); events.assertEmpty();
} }
@ -237,10 +232,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
Assert.assertEquals("", updateProfilePage.getLastName()); Assert.assertEquals("", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail()); Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm()) Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
else
Assert.assertEquals("Please specify last name.", updateProfilePage.getInputErrors().getLastNameError());
events.assertEmpty(); events.assertEmpty();
} }

View file

@ -1,51 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.actions;
import org.junit.Before;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
/**
* Only covers basic use cases for App Initialized actions. Complete dynamic user profile behavior is tested in {@link RequiredActionUpdateProfileWithUserProfileTest} as it shares same code as the App initialized action.
*
* @author Vlastimil Elias <velias@redhat.com>
*
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class AppInitiatedActionUpdateProfileWithUserProfileTest extends AppInitiatedActionUpdateProfileTest {
@Override
protected boolean isDynamicForm() {
return true;
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
}
@Before
public void beforeTest() {
VerifyProfileTest.setUserProfileConfiguration(testRealm(),null);
super.beforeTest();
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.actions;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public class RequiredActionUpdateEmailUserProfileTest extends RequiredActionUpdateEmailTest {
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
}
}

View file

@ -29,6 +29,7 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
@ -36,6 +37,7 @@ import org.keycloak.models.UserModel;
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.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
@ -67,10 +69,6 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
@Page @Page
protected ErrorPage errorPage; protected ErrorPage errorPage;
protected boolean isDynamicForm() {
return false;
}
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name()); ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name());
@ -177,11 +175,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
Assert.assertEquals("", updateProfilePage.getFirstName()); Assert.assertEquals("", updateProfilePage.getFirstName());
Assert.assertEquals("New last", updateProfilePage.getLastName()); Assert.assertEquals("New last", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail()); Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
if(isDynamicForm())
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
else
Assert.assertEquals("Please specify first name.", updateProfilePage.getInputErrors().getFirstNameError());
events.assertEmpty(); events.assertEmpty();
} }
@ -203,10 +197,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
Assert.assertEquals("", updateProfilePage.getLastName()); Assert.assertEquals("", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail()); Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm()) Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
else
Assert.assertEquals("Please specify last name.", updateProfilePage.getInputErrors().getLastNameError());
events.assertEmpty(); events.assertEmpty();
} }
@ -350,37 +341,47 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
@Test @Test
public void updateProfileWithoutRemoveCustomAttributes() { public void updateProfileWithoutRemoveCustomAttributes() {
UserRepresentation userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); UserProfileResource upResource = adminClient.realm("test").users().userProfile();
UserResource user = adminClient.realm("test").users().get(userRep.getId()); UPConfig upConfig = upResource.getConfiguration();
upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT);
upResource.update(upConfig);
userRep.setAttributes(new HashMap<>()); try {
userRep.getAttributes().put("custom", Arrays.asList("custom")); UserRepresentation userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
UserResource user = adminClient.realm("test").users().get(userRep.getId());
user.update(userRep); userRep.setAttributes(new HashMap<>());
userRep.getAttributes().put("custom", Arrays.asList("custom"));
loginPage.open(); user.update(userRep);
loginPage.login("test-user@localhost", "password"); loginPage.open();
updateProfilePage.assertCurrent(); loginPage.login("test-user@localhost", "password");
assertFalse(updateProfilePage.isCancelDisplayed());
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit(); updateProfilePage.assertCurrent();
assertFalse(updateProfilePage.isCancelDisplayed());
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name()).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent(); updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name()).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
events.expectLogin().assertEvent(); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
// assert user is really updated in persistent store events.expectLogin().assertEvent();
userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
Assert.assertEquals("New first", userRep.getFirstName()); // assert user is really updated in persistent store
Assert.assertEquals("New last", userRep.getLastName()); userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
Assert.assertEquals("new@email.com", userRep.getEmail()); Assert.assertEquals("New first", userRep.getFirstName());
Assert.assertEquals("test-user@localhost", userRep.getUsername()); Assert.assertEquals("New last", userRep.getLastName());
Assert.assertNotNull(userRep.getAttributes()); Assert.assertEquals("new@email.com", userRep.getEmail());
Assert.assertTrue(userRep.getAttributes().containsKey("custom")); Assert.assertEquals("test-user@localhost", userRep.getUsername());
Assert.assertNotNull(userRep.getAttributes());
Assert.assertTrue(userRep.getAttributes().containsKey("custom"));
} finally {
upConfig.setUnmanagedAttributePolicy(null);
upResource.update(upConfig);
}
} }
} }

View file

@ -35,31 +35,39 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
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.models.UserModel;
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.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.forms.RegisterWithUserProfileTest; import org.keycloak.testsuite.forms.RegisterWithUserProfileTest;
import org.keycloak.testsuite.forms.VerifyProfileTest; import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.util.ClientScopeBuilder; import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.keycloak.testsuite.util.KeycloakModelUtils; import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.testsuite.util.UserBuilder;
import org.openqa.selenium.By; import org.openqa.selenium.By;
/** /**
* Test update-profile required action with custom user profile configurations
* *
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
*
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE) public class RequiredActionUpdateProfileWithUserProfileTest extends AbstractTestRealmKeycloakTest {
public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActionUpdateProfileTest {
protected static final String PASSWORD = "password"; protected static final String PASSWORD = "password";
protected static final String USERNAME1 = "test-user@localhost"; protected static final String USERNAME1 = "test-user@localhost";
@ -67,17 +75,23 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
private static ClientRepresentation client_scope_default; private static ClientRepresentation client_scope_default;
private static ClientRepresentation client_scope_optional; private static ClientRepresentation client_scope_optional;
@Override @Rule
protected boolean isDynamicForm() { public AssertEvents events = new AssertEvents(this);
return true;
} @Page
protected AppPage appPage;
@Page
protected LoginPage loginPage;
@Page
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
@Page
protected ErrorPage errorPage;
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
testRealm.setClientScopes(new ArrayList<>()); testRealm.setClientScopes(new ArrayList<>());
testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_DEPARTMENT).protocol("openid-connect").build()); testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_DEPARTMENT).protocol("openid-connect").build());
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("profile").protocol("openid-connect").build()); testRealm.getClientScopes().add(ClientScopeBuilder.create().name("profile").protocol("openid-connect").build());
@ -94,7 +108,26 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
@Before @Before
public void beforeTest() { public void beforeTest() {
VerifyProfileTest.setUserProfileConfiguration(testRealm(),null); VerifyProfileTest.setUserProfileConfiguration(testRealm(),null);
super.beforeTest();
ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost");
UserRepresentation user = UserBuilder.create().enabled(true)
.username("test-user@localhost")
.email("test-user@localhost")
.firstName("Tom")
.lastName("Brady")
.emailVerified(true)
.requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.name()).build();
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
ApiUtil.removeUserByUsername(testRealm(), "john-doh@localhost");
user = UserBuilder.create().enabled(true)
.username("john-doh@localhost")
.email("john-doh@localhost")
.firstName("John")
.lastName("Doh")
.emailVerified(true)
.requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.name()).build();
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
} }
@Test @Test
@ -619,16 +652,6 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
assertEquals("LastCC", user.getLastName()); assertEquals("LastCC", user.getLastName());
} }
@Test
public void updateProfileWithoutRemoveCustomAttributes() {
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"custom\"," + PERMISSIONS_ALL + "}"
+ "]}");
super.updateProfileWithoutRemoveCustomAttributes();
}
protected void setUserProfileConfiguration(String configuration) { protected void setUserProfileConfiguration(String configuration) {
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration); VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
} }

View file

@ -24,12 +24,14 @@ import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.keycloak.admin.client.resource.UserProfileResource;
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.testsuite.AbstractAuthTest; import org.keycloak.testsuite.AbstractAuthTest;
import org.keycloak.testsuite.adapter.page.AppServerContextRoot; import org.keycloak.testsuite.adapter.page.AppServerContextRoot;
import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.ServerURLs; import org.keycloak.testsuite.util.ServerURLs;
import java.io.IOException; import java.io.IOException;
@ -120,6 +122,14 @@ public abstract class AbstractAdapterTest extends AbstractAuthTest {
} }
} }
@Before
public void enableUnmanagedAttributes() {
for (RealmRepresentation realm : adminClient.realms().findAll()) {
UserProfileResource upResource = adminClient.realm(realm.getRealm()).users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(upResource);
}
}
// TODO: Fix to not require re-import // TODO: Fix to not require re-import
@Override @Override
protected boolean isImportAfterEachMethod() { protected boolean isImportAfterEachMethod() {

View file

@ -9,10 +9,8 @@ import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL; import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
import static org.keycloak.testsuite.forms.VerifyProfileTest.setUserProfileConfiguration; import static org.keycloak.testsuite.forms.VerifyProfileTest.setUserProfileConfiguration;
import org.junit.After; import org.junit.After;
@ -21,7 +19,6 @@ import org.junit.Test;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
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;
import org.keycloak.common.Profile;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
@ -29,7 +26,6 @@ import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.DeclarativeUserProfileProvider; import org.keycloak.userprofile.DeclarativeUserProfileProvider;
@ -50,7 +46,6 @@ import jakarta.ws.rs.core.Response;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class DeclarativeUserTest extends AbstractAdminTest { public class DeclarativeUserTest extends AbstractAdminTest {
private static final String TEST_REALM_USER_MANAGER_NAME = "test-realm-user-manager"; private static final String TEST_REALM_USER_MANAGER_NAME = "test-realm-user-manager";
@ -63,7 +58,6 @@ public class DeclarativeUserTest extends AbstractAdminTest {
RealmRepresentation realmRep = realm.toRepresentation(); RealmRepresentation realmRep = realm.toRepresentation();
realmRep.setInternationalizationEnabled(true); realmRep.setInternationalizationEnabled(true);
realmRep.setSupportedLocales(new HashSet<>(Arrays.asList("en", "de"))); realmRep.setSupportedLocales(new HashSet<>(Arrays.asList("en", "de")));
enableDynamicUserProfile(realmRep);
realm.update(realmRep); realm.update(realmRep);
setUserProfileConfiguration(realm, "{\"attributes\": [" setUserProfileConfiguration(realm, "{\"attributes\": ["
+ "{\"name\": \"username\", " + PERMISSIONS_ALL + "}," + "{\"name\": \"username\", " + PERMISSIONS_ALL + "},"

View file

@ -27,6 +27,7 @@ import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.CibaConfig;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.credential.OTPCredentialModel; import org.keycloak.models.credential.OTPCredentialModel;
@ -302,33 +303,25 @@ public class PermissionsTest extends AbstractKeycloakTest {
} }
}, Resource.REALM, false, true); }, Resource.REALM, false, true);
try (RealmAttributeUpdater updater = new RealmAttributeUpdater(adminClient.realm(REALM_NAME)) RealmRepresentation realm = clients.get(AdminRoles.QUERY_REALMS).realm(REALM_NAME).toRepresentation();
.setAttribute(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString()) assertGettersEmpty(realm);
.update()) { assertNull(realm.isRegistrationEmailAsUsername());
RealmRepresentation realm = clients.get(AdminRoles.QUERY_REALMS).realm(REALM_NAME).toRepresentation(); assertNull(realm.getAttributes());
assertGettersEmpty(realm);
assertNull(realm.isRegistrationEmailAsUsername());
assertNull(realm.getAttributes());
realm = clients.get(AdminRoles.VIEW_USERS).realm(REALM_NAME).toRepresentation(); realm = clients.get(AdminRoles.VIEW_USERS).realm(REALM_NAME).toRepresentation();
assertNotNull(realm.isRegistrationEmailAsUsername()); assertNotNull(realm.isRegistrationEmailAsUsername());
assertNotNull(realm.getAttributes());
assertNotNull(realm.getAttributes().get(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED));
realm = clients.get(AdminRoles.MANAGE_USERS).realm(REALM_NAME).toRepresentation(); realm = clients.get(AdminRoles.MANAGE_USERS).realm(REALM_NAME).toRepresentation();
assertNotNull(realm.isRegistrationEmailAsUsername()); assertNotNull(realm.isRegistrationEmailAsUsername());
assertNotNull(realm.getAttributes());
assertNotNull(realm.getAttributes().get(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED));
// query users only if granted through fine-grained admin // query users only if granted through fine-grained admin
realm = clients.get(AdminRoles.QUERY_USERS).realm(REALM_NAME).toRepresentation(); realm = clients.get(AdminRoles.QUERY_USERS).realm(REALM_NAME).toRepresentation();
assertNull(realm.isRegistrationEmailAsUsername()); assertNull(realm.isRegistrationEmailAsUsername());
assertNull(realm.getAttributes()); assertNull(realm.getAttributes());
}
// this should pass given that users granted with "query" roles are allowed to access the realm with limited access // this should pass given that users granted with "query" roles are allowed to access the realm with limited access
for (String role : AdminRoles.ALL_QUERY_ROLES) { for (String role : AdminRoles.ALL_QUERY_ROLES) {
invoke(realm -> clients.get(role).realms().realm(REALM_NAME).toRepresentation(), clients.get(role), true); invoke(realmm -> clients.get(role).realms().realm(REALM_NAME).toRepresentation(), clients.get(role), true);
} }
invoke(new Invocation() { invoke(new Invocation() {

View file

@ -17,11 +17,11 @@
package org.keycloak.testsuite.admin; package org.keycloak.testsuite.admin;
import jakarta.ws.rs.WebApplicationException;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.After; import org.junit.After;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
@ -32,9 +32,9 @@ import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.IdentityProviderResource; import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.RoleMappingResource;
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;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
@ -63,12 +63,17 @@ import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserProfileMetadata;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory; import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.federation.UserMapStorageFactory; import org.keycloak.testsuite.federation.UserMapStorageFactory;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.page.LoginPasswordUpdatePage; import org.keycloak.testsuite.page.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.InfoPage; import org.keycloak.testsuite.pages.InfoPage;
@ -87,6 +92,7 @@ import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.validator.UsernameProhibitedCharactersValidator;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
@ -180,8 +186,18 @@ public class UserTest extends AbstractAdminTest {
} }
@Before @Before
public void beforeUserTest() { public void beforeUserTest() throws IOException {
createAppClientInRealm(REALM_NAME); createAppClientInRealm(REALM_NAME);
VerifyProfileTest.setUserProfileConfiguration(realm, null);
UPConfig upConfig = realm.users().userProfile().getConfiguration();
for (String name : managedAttributes) {
upConfig.addOrReplaceAttribute(createAttributeMetadata(name));
}
VerifyProfileTest.setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig));
assertAdminEvents.clear(); assertAdminEvents.clear();
} }
@ -1241,16 +1257,27 @@ public class UserTest extends AbstractAdminTest {
@Test @Test
public void wildcardSearch() { public void wildcardSearch() {
Assume.assumeFalse("Default validators do not allow special chars", isDeclarativeUserProfile()); UserProfileResource upResource = realm.users().userProfile();
createUser("0user\\\\0", "email0@emal"); UPConfig upConfig = upResource.getConfiguration();
createUser("1user\\\\", "email1@emal"); Map<String, Object> prohibitedCharsOrigCfg = upConfig.getAttribute(UserModel.USERNAME).getValidations().get(UsernameProhibitedCharactersValidator.ID);
createUser("2user\\\\%", "email2@emal"); upConfig.getAttribute(UserModel.USERNAME).getValidations().remove(UsernameProhibitedCharactersValidator.ID);
createUser("3user\\\\*", "email3@emal"); upResource.update(upConfig);
createUser("4user\\\\_", "email4@emal"); assertAdminEvents.clear();
assertThat(realm.users().search("*", null, null), hasSize(5)); try {
assertThat(realm.users().search("*user\\", null, null), hasSize(5)); createUser("0user\\\\0", "email0@emal");
assertThat(realm.users().search("\"2user\\\\%\"", null, null), hasSize(1)); createUser("1user\\\\", "email1@emal");
createUser("2user\\\\%", "email2@emal");
createUser("3user\\\\*", "email3@emal");
createUser("4user\\\\_", "email4@emal");
assertThat(realm.users().search("*", null, null), hasSize(5));
assertThat(realm.users().search("*user\\", null, null), hasSize(5));
assertThat(realm.users().search("\"2user\\\\%\"", null, null), hasSize(1));
} finally {
upConfig.getAttribute(UserModel.USERNAME).addValidation(UsernameProhibitedCharactersValidator.ID, prohibitedCharsOrigCfg);
upResource.update(upConfig);
}
} }
@Test @Test
@ -2465,20 +2492,16 @@ public class UserTest extends AbstractAdminTest {
UserRepresentation update = new UserRepresentation(); UserRepresentation update = new UserRepresentation();
update.setId(userId); update.setId(userId);
if (isDeclarativeUserProfile()) { // user profile requires sending all attributes otherwise they are removed
// user profile requires sending all attributes otherwise they are removed update.setEmail(email);
update.setEmail(email);
}
update.setAttributes(Map.of("phoneNumber", List.of("123"))); update.setAttributes(Map.of("phoneNumber", List.of("123")));
updateUser(realm.users().get(userId), update); updateUser(realm.users().get(userId), update);
UserRepresentation updated = realm.users().get(userId).toRepresentation(); UserRepresentation updated = realm.users().get(userId).toRepresentation();
assertThat(updated.getUsername(), equalTo(userName)); assertThat(updated.getUsername(), equalTo(userName));
if (isDeclarativeUserProfile()) { assertThat(updated.getAttributes().get("phoneNumber"), equalTo(List.of("123")));
assertThat(updated.getAttributes().get("phoneNumber"), equalTo(List.of("123")));
} else {
assertThat(updated.getAttributes(), equalTo(Map.of("phoneNumber", List.of("123"))));
}
assertThat(updated.getEmail(), equalTo(email)); assertThat(updated.getEmail(), equalTo(email));
} }
@ -2494,9 +2517,7 @@ public class UserTest extends AbstractAdminTest {
try { try {
updateUser(user, userRep); updateUser(user, userRep);
if (isDeclarativeUserProfile()) { fail("Should fail because realm does not allow edit username");
fail("Should fail because realm does not allow edit username");
}
} catch (BadRequestException expected) { } catch (BadRequestException expected) {
ErrorRepresentation error = expected.getResponse().readEntity(ErrorRepresentation.class); ErrorRepresentation error = expected.getResponse().readEntity(ErrorRepresentation.class);
assertEquals("error-user-attribute-read-only", error.getErrorMessage()); assertEquals("error-user-attribute-read-only", error.getErrorMessage());
@ -2869,26 +2890,42 @@ public class UserTest extends AbstractAdminTest {
@Test @Test
public void defaultMaxResults() { public void defaultMaxResults() {
UsersResource users = adminClient.realms().realm("test").users(); UserProfileResource upResource = adminClient.realm("test").users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
upConfig.addOrReplaceAttribute(createAttributeMetadata("aName"));
upResource.update(upConfig);
for (int i = 0; i < 110; i++) { try {
users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close(); UsersResource users = adminClient.realms().realm("test").users();
for (int i = 0; i < 110; i++) {
users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close();
}
List<UserRepresentation> result = users.search("test", null, null);
assertEquals(100, result.size());
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.notNullValue());
assertThat(user.getAttributes().keySet(), Matchers.hasSize(1));
assertThat(user.getAttributes(), Matchers.hasEntry(is("aName"), Matchers.contains("aValue")));
}
assertEquals(105, users.search("test", 0, 105).size());
assertEquals(111, users.search("test", 0, 1000).size());
} finally {
upConfig.removeAttribute("aName");
upResource.update(upConfig);
} }
List<UserRepresentation> result = users.search("test", null, null);
assertEquals(100, result.size());
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.notNullValue());
assertThat(user.getAttributes().keySet(), Matchers.hasSize(1));
assertThat(user.getAttributes(), Matchers.hasEntry(is("aName"), Matchers.contains("aValue")));
}
assertEquals(105, users.search("test", 0, 105).size());
assertEquals(111, users.search("test", 0, 1000).size());
} }
@Test @Test
public void defaultMaxResultsBrief() { public void defaultMaxResultsBrief() {
UserProfileResource upResource = adminClient.realm("test").users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
upConfig.addOrReplaceAttribute(createAttributeMetadata("aName"));
upResource.update(upConfig);
try {
UsersResource users = adminClient.realms().realm("test").users(); UsersResource users = adminClient.realms().realm("test").users();
for (int i = 0; i < 110; i++) { for (int i = 0; i < 110; i++) {
@ -2900,6 +2937,10 @@ public class UserTest extends AbstractAdminTest {
for (UserRepresentation user : result) { for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.nullValue()); assertThat(user.getAttributes(), Matchers.nullValue());
} }
} finally {
upConfig.removeAttribute("aName");
upResource.update(upConfig);
}
} }
@Test @Test
@ -3461,7 +3502,75 @@ public class UserTest extends AbstractAdminTest {
); );
} }
protected boolean isDeclarativeUserProfile() { @Test
return false; public void testUserProfileMetadata() {
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = realm.users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
for (String name : managedAttributes) {
assertNotNull(metadata.getAttributeMetadata(name));
}
}
@Test
public void testUsernameReadOnlyIfEmailAsUsernameEnabled() {
switchRegistrationEmailAsUsername(true);
getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false));
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = realm.users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
UserProfileAttributeMetadata username = metadata.getAttributeMetadata(UserModel.USERNAME);
assertNotNull(username);
assertTrue(username.isReadOnly());
UserProfileAttributeMetadata email = metadata.getAttributeMetadata(UserModel.EMAIL);
assertNotNull(email);
assertFalse(email.isReadOnly());
}
@Test
public void testEmailNotReadOnlyIfEmailAsUsernameEnabledAndEditUsernameDisabled() {
switchRegistrationEmailAsUsername(true);
getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false));
RealmRepresentation rep = realm.toRepresentation();
assertFalse(rep.isEditUsernameAllowed());
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = realm.users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
UserProfileAttributeMetadata username = metadata.getAttributeMetadata(UserModel.USERNAME);
assertNotNull(username);
assertTrue(username.isReadOnly());
UserProfileAttributeMetadata email = metadata.getAttributeMetadata(UserModel.EMAIL);
assertNotNull(email);
assertFalse(email.isReadOnly());
}
@Test
public void testDefaultCharacterValidationOnUsername() {
List<String> invalidNames = List.of("1user\\\\", "2user\\\\%", "3user\\\\*", "4user\\\\_");
for (String invalidName : invalidNames) {
try {
createUser(invalidName, "test@invalid.org");
fail("Should fail because the username contains invalid characters");
} catch (WebApplicationException bre) {
assertEquals(400, bre.getResponse().getStatus());
ErrorRepresentation error = bre.getResponse().readEntity(ErrorRepresentation.class);
assertEquals("error-username-invalid-character", error.getErrorMessage());
}
}
}
private UPAttribute createAttributeMetadata(String name) {
UPAttribute attribute = new UPAttribute();
attribute.setName(name);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Set.of("user", "admin"));
attribute.setPermissions(permissions);
this.managedAttributes.add(name);
return attribute;
} }
} }

View file

@ -1,147 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.admin;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import jakarta.ws.rs.WebApplicationException;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.Profile.Feature;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserProfileMetadata;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.util.JsonSerialization;
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class UserTestWithUserProfile extends UserTest {
@Before
public void onBefore() throws IOException {
RealmRepresentation realmRep = realm.toRepresentation();
VerifyProfileTest.disableDynamicUserProfile(realm);
assertAdminEvents.poll(); // update realm
assertAdminEvents.poll(); // set UP configuration
VerifyProfileTest.enableDynamicUserProfile(realmRep);
realm.update(realmRep);
assertAdminEvents.poll();
VerifyProfileTest.setUserProfileConfiguration(realm, null);
assertAdminEvents.poll();
UPConfig upConfig = realm.users().userProfile().getConfiguration();
for (String name : managedAttributes) {
upConfig.addOrReplaceAttribute(createAttributeMetadata(name));
}
VerifyProfileTest.setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig));
assertAdminEvents.poll();
}
@Test
public void testUserProfileMetadata() {
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = realm.users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
for (String name : managedAttributes) {
assertNotNull(metadata.getAttributeMetadata(name));
}
}
@Test
public void testUsernameReadOnlyIfEmailAsUsernameEnabled() {
switchRegistrationEmailAsUsername(true);
getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false));
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = realm.users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
UserProfileAttributeMetadata username = metadata.getAttributeMetadata(UserModel.USERNAME);
assertNotNull(username);
assertTrue(username.isReadOnly());
UserProfileAttributeMetadata email = metadata.getAttributeMetadata(UserModel.EMAIL);
assertNotNull(email);
assertFalse(email.isReadOnly());
}
@Test
public void testEmailNotReadOnlyIfEmailAsUsernameEnabledAndEditUsernameDisabled() {
switchRegistrationEmailAsUsername(true);
getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false));
RealmRepresentation rep = realm.toRepresentation();
assertFalse(rep.isEditUsernameAllowed());
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = realm.users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
UserProfileAttributeMetadata username = metadata.getAttributeMetadata(UserModel.USERNAME);
assertNotNull(username);
assertTrue(username.isReadOnly());
UserProfileAttributeMetadata email = metadata.getAttributeMetadata(UserModel.EMAIL);
assertNotNull(email);
assertFalse(email.isReadOnly());
}
private UPAttribute createAttributeMetadata(String name) {
UPAttribute attribute = new UPAttribute();
attribute.setName(name);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Set.of("user", "admin"));
attribute.setPermissions(permissions);
this.managedAttributes.add(name);
return attribute;
}
@Test
public void testDefaultCharacterValidationOnUsername() {
List<String> invalidNames = List.of("1user\\\\", "2user\\\\%", "3user\\\\*", "4user\\\\_");
for (String invalidName : invalidNames) {
try {
createUser(invalidName, "test@invalid.org");
fail("Should fail because the username contains invalid characters");
} catch (WebApplicationException bre) {
assertEquals(400, bre.getResponse().getStatus());
ErrorRepresentation error = bre.getResponse().readEntity(ErrorRepresentation.class);
assertEquals("error-username-invalid-character", error.getErrorMessage());
}
}
}
@Override
protected boolean isDeclarativeUserProfile() {
return true;
}
}

View file

@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.GroupsResource; import org.keycloak.admin.client.resource.GroupsResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.RoleMappingResource;
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;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
@ -39,7 +40,9 @@ import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.updaters.Creator; import org.keycloak.testsuite.updaters.Creator;
import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
@ -1281,6 +1284,10 @@ public class GroupTest extends AbstractGroupTest {
String groupName = "brief-grouptest-group"; String groupName = "brief-grouptest-group";
String userName = "brief-grouptest-user"; String userName = "brief-grouptest-user";
// enable user profile unmanaged attributes
UserProfileResource upResource = realm.users().userProfile();
UPConfig cfg = VerifyProfileTest.enableUnmanagedAttributes(upResource);
GroupsResource groups = realm.groups(); GroupsResource groups = realm.groups();
try (Response response = groups.add(GroupBuilder.create().name(groupName).build())) { try (Response response = groups.add(GroupBuilder.create().name(groupName).build())) {
String groupId = ApiUtil.getCreatedId(response); String groupId = ApiUtil.getCreatedId(response);
@ -1308,6 +1315,9 @@ public class GroupTest extends AbstractGroupTest {
group.remove(); group.remove();
user.remove(); user.remove();
} finally {
cfg.setUnmanagedAttributePolicy(null);
upResource.update(cfg);
} }
} }

View file

@ -23,23 +23,19 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import static org.keycloak.userprofile.config.UPConfigUtils.readDefaultConfig; import static org.keycloak.userprofile.config.UPConfigUtils.readDefaultConfig;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource; import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.common.Profile;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeGroupMetadata; import org.keycloak.representations.idm.UserProfileAttributeGroupMetadata;
import org.keycloak.representations.idm.UserProfileMetadata; import org.keycloak.representations.idm.UserProfileMetadata;
import org.keycloak.testsuite.admin.AbstractAdminTest; import org.keycloak.testsuite.admin.AbstractAdminTest;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.representations.userprofile.config.UPAttribute; import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPGroup; import org.keycloak.representations.userprofile.config.UPGroup;
@ -49,15 +45,10 @@ import org.keycloak.userprofile.config.UPConfigUtils;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class UserProfileAdminTest extends AbstractAdminTest { public class UserProfileAdminTest extends AbstractAdminTest {
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
} }
@Test @Test

View file

@ -31,6 +31,7 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.IdpConfirmLinkPage; import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
import org.keycloak.testsuite.pages.IdpLinkEmailPage; import org.keycloak.testsuite.pages.IdpLinkEmailPage;
@ -179,8 +180,13 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
@Before @Before
public void beforeBrokerTest() { public void beforeBrokerTest() {
importRealm(bc.createConsumerRealm()); RealmRepresentation consumerRealm = bc.createConsumerRealm();
importRealm(bc.createProviderRealm()); RealmRepresentation providerRealm = bc.createProviderRealm();
importRealm(consumerRealm);
importRealm(providerRealm);
VerifyProfileTest.enableUnmanagedAttributes(adminClient.realm(consumerRealm.getRealm()).users().userProfile());
VerifyProfileTest.enableUnmanagedAttributes(adminClient.realm(providerRealm.getRealm()).users().userProfile());
} }
@After @After

View file

@ -65,17 +65,6 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
protected void enableDynamicUserProfile() {
RealmResource rr = adminClient.realm(bc.consumerRealmName());
RealmRepresentation testRealm = rr.toRepresentation();
VerifyProfileTest.enableDynamicUserProfile(testRealm);
rr.update(testRealm);
}
/** /**
* Refers to in old test suite: org.keycloak.testsuite.broker.AbstractFirstBrokerLoginTest#testErrorPageWhenDuplicationNotAllowed_updateProfileOn * Refers to in old test suite: org.keycloak.testsuite.broker.AbstractFirstBrokerLoginTest#testErrorPageWhenDuplicationNotAllowed_updateProfileOn

View file

@ -114,8 +114,8 @@ public class KcOidcFirstBrokerLoginDetectExistingUserTest extends AbstractInitia
public void loginWhenUserExistsOnConsumer() { public void loginWhenUserExistsOnConsumer() {
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin); updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
final String firstname = "Firstname(loginWhenUserExistsOnConsumer)"; final String firstname = "Firstname_loginWhenUserExistsOnConsumer";
final String lastname = "Lastname(loginWhenUserExistsOnConsumer)"; final String lastname = "Lastname_loginWhenUserExistsOnConsumer";
final String username = "firstandlastname"; final String username = "firstandlastname";
final String email = "firstnamelastname@example.org"; final String email = "firstnamelastname@example.org";
createUser(bc.providerRealmName(), username, BrokerTestConstants.USER_PASSWORD, firstname, lastname, email); createUser(bc.providerRealmName(), username, BrokerTestConstants.USER_PASSWORD, firstname, lastname, email);

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import org.apache.commons.lang3.StringUtils;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
@ -7,16 +8,25 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.forms.RegisterWithUserProfileTest;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage; import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.NoSuchElementException;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername; import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import static org.keycloak.testsuite.forms.VerifyProfileTest.ATTRIBUTE_DEPARTMENT;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -340,4 +350,384 @@ public class KcOidcFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
assertTrue("We should be on the login page.", driver.getPageSource().contains("Sign in to your account")); assertTrue("We should be on the login page.", driver.getPageSource().contains("Sign in to your account"));
final var socialButton = this.loginPage.findSocialButton(bc.getIDPAlias()); final var socialButton = this.loginPage.findSocialButton(bc.getIDPAlias());
} }
@Test
public void testDisplayName() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", \"displayName\" : \"Department\", " + PERMISSIONS_ALL + ", \"required\":{}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//assert field names
// i18n replaced
org.junit.Assert.assertEquals("First name", updateAccountInformationPage.getLabelForField("firstName"));
// attribute name used if no display name set
org.junit.Assert.assertEquals("lastName", updateAccountInformationPage.getLabelForField("lastName"));
// direct value in display name
org.junit.Assert.assertEquals("Department", updateAccountInformationPage.getLabelForField("department"));
}
@Test
public void testAttributeGrouping() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"username\", " + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}, \"group\": \"company\"},"
+ "{\"name\": \"email\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"group\": \"contact\"}"
+ "], \"groups\": ["
+ "{\"name\": \"company\", \"displayDescription\": \"Company field desc\" },"
+ "{\"name\": \"contact\" }"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//assert fields location in form
String htmlFormId = "kc-idp-review-profile-form";
//assert fields and groups location in form
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(1) > div:nth-child(2) > input#lastName")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(2) > div:nth-child(2) > input#username")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(3) > div:nth-child(2) > input#firstName")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(1) > label#header-company")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(2) > label#description-company")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(5) > div:nth-child(2) > input#department")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(6) > div:nth-child(1) > label#header-contact")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(7) > div:nth-child(2) > input#email")
).isDisplayed()
);
}
@Test
public void testAttributeGuiOrder() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}},"
+ "{\"name\": \"username\", " + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"email\", " + VerifyProfileTest.PERMISSIONS_ALL + "}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//assert fields location in form
String htmlFormId = "kc-idp-review-profile-form";
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(1) > div:nth-child(2) > input#lastName")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(2) > div:nth-child(2) > input#department")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(3) > div:nth-child(2) > input#username")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(2) > input#firstName")
).isDisplayed()
);
org.junit.Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(5) > div:nth-child(2) > input#email")
).isDisplayed()
);
}
@Test
public void testAttributeInputTypes() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ RegisterWithUserProfileTest.UP_CONFIG_PART_INPUT_TYPES
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
RegisterWithUserProfileTest.assertFieldTypes(driver);
}
@Test
public void testDynamicUserProfileReviewWhenMissing_requiredReadOnlyAttributeDoesnotForceUpdate() {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
}
@Test
public void testDynamicUserProfileReviewWhenMissing_requiredButNotSelectedByScopeAttributeDoesnotForceUpdate() {
addDepartmentScopeIntoRealm();
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"department\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
}
@Test
public void testDynamicUserProfileReviewWhenMissing_requiredAndSelectedByScopeAttributeForcesUpdate() {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
}
@Test
public void testDynamicUserProfileReview_requiredReadOnlyAttributeNotRenderedAndNotBlockingProcess() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
org.junit.Assert.assertFalse(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration@email", "FirstAA", "LastAA");
}
@Test
public void testDynamicUserProfileReview_attributeRequiredAndSelectedByScopeMustBeSet() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//check required validation works
updateAccountInformationPage.updateAccountInformation( "attributeRequiredAndSelectedByScopeMustBeSet", "attributeRequiredAndSelectedByScopeMustBeSet@email", "FirstAA", "LastAA", "");
updateAccountInformationPage.assertCurrent();
updateAccountInformationPage.updateAccountInformation( "attributeRequiredAndSelectedByScopeMustBeSet", "attributeRequiredAndSelectedByScopeMustBeSet@email", "FirstAA", "LastAA", "DepartmentAA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeRequiredAndSelectedByScopeMustBeSet");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertEquals("DepartmentAA", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
}
@Test
public void testDynamicUserProfileReview_attributeNotRequiredAndSelectedByScopeCanBeIgnored() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
org.junit.Assert.assertTrue(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "FirstAA", "LastAA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeNotRequiredAndSelectedByScopeCanBeIgnored");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertThat(StringUtils.isEmpty(user.firstAttribute(ATTRIBUTE_DEPARTMENT)), is(true));
}
@Test
public void testDynamicUserProfileReview_attributeNotRequiredAndSelectedByScopeCanBeSet() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
org.junit.Assert.assertTrue(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "attributeNotRequiredAndSelectedByScopeCanBeSet", "attributeNotRequiredAndSelectedByScopeCanBeSet@email", "FirstAA", "LastAA","Department AA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeNotRequiredAndSelectedByScopeCanBeSet");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertEquals("Department AA", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
}
@Test
public void testDynamicUserProfileReview_attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingProcess() {
addDepartmentScopeIntoRealm();
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"department\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
org.junit.Assert.assertFalse(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration", "attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration@email", "FirstAA", "LastAA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertEquals(null, user.firstAttribute(ATTRIBUTE_DEPARTMENT));
}
public void addDepartmentScopeIntoRealm() {
testRealm().clientScopes().create(ClientScopeBuilder.create().name("department").protocol("openid-connect").build());
}
protected void setUserProfileConfiguration(String configuration) {
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
}
private RealmResource testRealm() {
return adminClient.realm(bc.consumerRealmName());
}
} }

View file

@ -1,434 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.broker;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import static org.keycloak.testsuite.forms.VerifyProfileTest.ATTRIBUTE_DEPARTMENT;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE;
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.RegisterWithUserProfileTest;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.openqa.selenium.By;
/**
*
* @author Vlastimil Elias <velias@redhat.com>
*
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class KcOidcFirstBrokerLoginWithUserProfileTest extends KcOidcFirstBrokerLoginTest {
@Override
@Before
public void beforeBrokerTest() {
super.beforeBrokerTest();
enableDynamicUserProfile();
}
@Test
public void testDisplayName() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", \"displayName\" : \"Department\", " + PERMISSIONS_ALL + ", \"required\":{}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//assert field names
// i18n replaced
Assert.assertEquals("First name", updateAccountInformationPage.getLabelForField("firstName"));
// attribute name used if no display name set
Assert.assertEquals("lastName", updateAccountInformationPage.getLabelForField("lastName"));
// direct value in display name
Assert.assertEquals("Department", updateAccountInformationPage.getLabelForField("department"));
}
@Test
public void testAttributeGrouping() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"username\", " + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}, \"group\": \"company\"},"
+ "{\"name\": \"email\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"group\": \"contact\"}"
+ "], \"groups\": ["
+ "{\"name\": \"company\", \"displayDescription\": \"Company field desc\" },"
+ "{\"name\": \"contact\" }"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//assert fields location in form
String htmlFormId = "kc-idp-review-profile-form";
//assert fields and groups location in form
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(1) > div:nth-child(2) > input#lastName")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(2) > div:nth-child(2) > input#username")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(3) > div:nth-child(2) > input#firstName")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(1) > label#header-company")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(2) > label#description-company")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(5) > div:nth-child(2) > input#department")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(6) > div:nth-child(1) > label#header-contact")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(7) > div:nth-child(2) > input#email")
).isDisplayed()
);
}
@Test
public void testAttributeGuiOrder() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}},"
+ "{\"name\": \"username\", " + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"email\", " + VerifyProfileTest.PERMISSIONS_ALL + "}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//assert fields location in form
String htmlFormId = "kc-idp-review-profile-form";
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(1) > div:nth-child(2) > input#lastName")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(2) > div:nth-child(2) > input#department")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(3) > div:nth-child(2) > input#username")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(2) > input#firstName")
).isDisplayed()
);
Assert.assertTrue(
driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(5) > div:nth-child(2) > input#email")
).isDisplayed()
);
}
@Test
public void testAttributeInputTypes() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ RegisterWithUserProfileTest.UP_CONFIG_PART_INPUT_TYPES
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
RegisterWithUserProfileTest.assertFieldTypes(driver);
}
@Test
public void testDynamicUserProfileReviewWhenMissing_requiredReadOnlyAttributeDoesnotForceUpdate() {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
}
@Test
public void testDynamicUserProfileReviewWhenMissing_requiredButNotSelectedByScopeAttributeDoesnotForceUpdate() {
addDepartmentScopeIntoRealm();
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"department\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
}
@Test
public void testDynamicUserProfileReviewWhenMissing_requiredAndSelectedByScopeAttributeForcesUpdate() {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
}
@Test
public void testDynamicUserProfileReview_requiredReadOnlyAttributeNotRenderedAndNotBlockingProcess() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", " + PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertFalse(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration@email", "FirstAA", "LastAA");
}
@Test
public void testDynamicUserProfileReview_attributeRequiredAndSelectedByScopeMustBeSet() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
//check required validation works
updateAccountInformationPage.updateAccountInformation( "attributeRequiredAndSelectedByScopeMustBeSet", "attributeRequiredAndSelectedByScopeMustBeSet@email", "FirstAA", "LastAA", "");
updateAccountInformationPage.assertCurrent();
updateAccountInformationPage.updateAccountInformation( "attributeRequiredAndSelectedByScopeMustBeSet", "attributeRequiredAndSelectedByScopeMustBeSet@email", "FirstAA", "LastAA", "DepartmentAA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeRequiredAndSelectedByScopeMustBeSet");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertEquals("DepartmentAA", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
}
@Test
public void testDynamicUserProfileReview_attributeNotRequiredAndSelectedByScopeCanBeIgnored() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertTrue(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "FirstAA", "LastAA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeNotRequiredAndSelectedByScopeCanBeIgnored");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertThat(StringUtils.isEmpty(user.firstAttribute(ATTRIBUTE_DEPARTMENT)), is(true));
}
@Test
public void testDynamicUserProfileReview_attributeNotRequiredAndSelectedByScopeCanBeSet() {
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
//we use 'profile' scope which is requested by default
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\"profile\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertTrue(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "attributeNotRequiredAndSelectedByScopeCanBeSet", "attributeNotRequiredAndSelectedByScopeCanBeSet@email", "FirstAA", "LastAA","Department AA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeNotRequiredAndSelectedByScopeCanBeSet");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertEquals("Department AA", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
}
@Test
public void testDynamicUserProfileReview_attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingProcess() {
addDepartmentScopeIntoRealm();
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"department\"]}}"
+ "]}");
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertFalse(updateAccountInformationPage.isDepartmentPresent());
updateAccountInformationPage.updateAccountInformation( "attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration", "attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration@email", "FirstAA", "LastAA");
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration");
assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName());
assertEquals(null, user.firstAttribute(ATTRIBUTE_DEPARTMENT));
}
public void addDepartmentScopeIntoRealm() {
testRealm().clientScopes().create(ClientScopeBuilder.create().name("department").protocol("openid-connect").build());
}
protected void setUserProfileConfiguration(String configuration) {
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
}
private RealmResource testRealm() {
return adminClient.realm(bc.consumerRealmName());
}
}

View file

@ -30,7 +30,7 @@ public class KcOidcUsernameTemplateMapperTest extends AbstractUsernameTemplateMa
@Override @Override
protected String getMapperTemplate() { protected String getMapperTemplate() {
return "kc-oidc-idp-[%s]"; return "kc-oidc-idp-%s";
} }
@Override @Override

View file

@ -1,38 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.broker;
import org.junit.Before;
import org.keycloak.common.Profile;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
/**
*
* @author Vlastimil Elias <velias@redhat.com>
*
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class KcSamlFirstBrokerLoginWithUserProfileTest extends KcSamlFirstBrokerLoginTest {
@Override
@Before
public void beforeBrokerTest() {
super.beforeBrokerTest();
enableDynamicUserProfile();
}
}

View file

@ -56,7 +56,7 @@ public class UsernameTemplateMapperTest extends AbstractBaseBrokerTest {
userTemplateImporterMapper.setName("custom-username-import-mapper"); userTemplateImporterMapper.setName("custom-username-import-mapper");
userTemplateImporterMapper.setIdentityProviderMapper(UsernameTemplateMapper.PROVIDER_ID); userTemplateImporterMapper.setIdentityProviderMapper(UsernameTemplateMapper.PROVIDER_ID);
userTemplateImporterMapper.setConfig(ImmutableMap.<String, String>builder() userTemplateImporterMapper.setConfig(ImmutableMap.<String, String>builder()
.put(UsernameTemplateMapper.TEMPLATE, "${ALIAS}:${CLAIM.sub}") .put(UsernameTemplateMapper.TEMPLATE, "${ALIAS}_${CLAIM.sub}")
.build()); .build());
IdentityProviderMapperRepresentation jwtClaimsAttrMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation jwtClaimsAttrMapper = new IdentityProviderMapperRepresentation();
@ -71,22 +71,6 @@ public class UsernameTemplateMapperTest extends AbstractBaseBrokerTest {
return Lists.newArrayList(userTemplateImporterMapper, jwtClaimsAttrMapper); return Lists.newArrayList(userTemplateImporterMapper, jwtClaimsAttrMapper);
} }
/**
* See: KEYCLOAK-8100
*/
@Test
public void usernameShouldBeDerivedFromAliasAndIdpSubClaim() {
logInAsUserInIDP();
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
UserRepresentation user = adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserEmail(), 0, 1).get(0);
String username = user.getUsername();
assertEquals("Should render alias:sub as Username", bc.getIDPAlias() + ":" + idpUserId, username);
}
/** /**
* See: KEYCLOAK-8100 * See: KEYCLOAK-8100
*/ */
@ -99,5 +83,8 @@ public class UsernameTemplateMapperTest extends AbstractBaseBrokerTest {
UserRepresentation user = adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserEmail(), 0, 1).get(0); UserRepresentation user = adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserEmail(), 0, 1).get(0);
assertEquals("Should render idpSub as mappedSub attribute", idpUserId, user.getAttributes().get("mappedSub").get(0)); assertEquals("Should render idpSub as mappedSub attribute", idpUserId, user.getAttributes().get("mappedSub").get(0));
String username = user.getUsername();
assertEquals("Should render alias:sub as Username", bc.getIDPAlias() + "_" + idpUserId, username);
} }
} }

View file

@ -31,6 +31,7 @@ public abstract class AbstractInvalidationClusterTest<T, TR> extends AbstractClu
RealmRepresentation testRealm = new RealmRepresentation(); RealmRepresentation testRealm = new RealmRepresentation();
testRealm.setRealm("test_" + RandomStringUtils.randomAlphabetic(5)); testRealm.setRealm("test_" + RandomStringUtils.randomAlphabetic(5));
testRealm.setEnabled(true); testRealm.setEnabled(true);
testRealm.setEditUsernameAllowed(true);
return testRealm; return testRealm;
} }

View file

@ -265,13 +265,7 @@ public class ExportImportTest extends AbstractKeycloakTest {
@Test @Test
public void testExportUserProfileConfig() throws IOException { public void testExportUserProfileConfig() throws IOException {
//Enable user profile on realm
RealmResource realmRes = adminClient.realm(TEST_REALM); RealmResource realmRes = adminClient.realm(TEST_REALM);
RealmRepresentation realmRep = realmRes.toRepresentation();
Map<String, String> realmAttr = realmRep.getAttributesOrEmpty();
realmAttr.put(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
realmRep.setAttributes(realmAttr);
realmRes.update(realmRep);
//add some non-default config //add some non-default config
UPConfig persistedConfig = VerifyProfileTest.setUserProfileConfiguration(realmRes, VerifyProfileTest.CONFIGURATION_FOR_USER_EDIT); UPConfig persistedConfig = VerifyProfileTest.setUserProfileConfiguration(realmRes, VerifyProfileTest.CONFIGURATION_FOR_USER_EDIT);

View file

@ -52,7 +52,6 @@ import static org.keycloak.userprofile.UserProfileUtil.USER_METADATA_GROUP;
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest { public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-standalone-connection.properties"; private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-standalone-connection.properties";
@ -188,39 +187,31 @@ public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
@Test @Test
public void testUserProfile() throws Exception { public void testUserProfile() throws Exception {
RealmRepresentation realm = testRealmResource().toRepresentation(); assertSuccessfulSpnegoLogin("hnelson", "hnelson", "secret");
VerifyProfileTest.enableDynamicUserProfile(realm);
testRealmResource().update(realm);
try { // User-profile data should be present (including KERBEROS_PRINCIPAL attribute)
assertSuccessfulSpnegoLogin("hnelson", "hnelson", "secret"); UserResource johnResource = ApiUtil.findUserByUsernameId(testRealmResource(), "hnelson");
UserRepresentation john = johnResource.toRepresentation(true);
Assert.assertNames(john.getUserProfileMetadata().getAttributes(), UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.EMAIL, UserModel.USERNAME, KerberosConstants.KERBEROS_PRINCIPAL);
// User-profile data should be present (including KERBEROS_PRINCIPAL attribute) // KERBEROS_PRINCIPAL attribute should be read-only and should be in "User metadata" group
UserResource johnResource = ApiUtil.findUserByUsernameId(testRealmResource(), "hnelson"); UserProfileAttributeMetadata krbPrincipalAttribute = john.getUserProfileMetadata().getAttributeMetadata(KerberosConstants.KERBEROS_PRINCIPAL);
UserRepresentation john = johnResource.toRepresentation(true); Assert.assertTrue(krbPrincipalAttribute.isReadOnly());
Assert.assertNames(john.getUserProfileMetadata().getAttributes(), UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.EMAIL, UserModel.USERNAME, KerberosConstants.KERBEROS_PRINCIPAL); Assert.assertEquals(USER_METADATA_GROUP, krbPrincipalAttribute.getGroup());
// KERBEROS_PRINCIPAL attribute should be read-only and should be in "User metadata" group // Test Update profile
UserProfileAttributeMetadata krbPrincipalAttribute = john.getUserProfileMetadata().getAttributeMetadata(KerberosConstants.KERBEROS_PRINCIPAL); john.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PROFILE.toString());
Assert.assertTrue(krbPrincipalAttribute.isReadOnly()); johnResource.update(john);
Assert.assertEquals(USER_METADATA_GROUP, krbPrincipalAttribute.getGroup());
// Test Update profile Response spnegoResponse = spnegoLogin("hnelson", "secret");
john.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PROFILE.toString()); Assert.assertEquals(200, spnegoResponse.getStatus());
johnResource.update(john); String responseText = spnegoResponse.readEntity(String.class);
Assert.assertTrue(responseText.contains("You need to update your user profile to activate your account."));
Assert.assertFalse(responseText.contains("KERBEROS_PRINCIPAL"));
spnegoResponse.close();
Response spnegoResponse = spnegoLogin("hnelson", "secret"); john.getRequiredActions().remove(UserModel.RequiredAction.UPDATE_PROFILE.toString());
Assert.assertEquals(200, spnegoResponse.getStatus()); johnResource.update(john);
String responseText = spnegoResponse.readEntity(String.class);
Assert.assertTrue(responseText.contains("You need to update your user profile to activate your account."));
Assert.assertFalse(responseText.contains("KERBEROS_PRINCIPAL"));
spnegoResponse.close();
john.getRequiredActions().remove(UserModel.RequiredAction.UPDATE_PROFILE.toString());
johnResource.update(john);
} finally {
VerifyProfileTest.disableDynamicUserProfile(testRealmResource());
}
} }
} }

View file

@ -19,7 +19,6 @@
package org.keycloak.testsuite.federation.ldap; package org.keycloak.testsuite.federation.ldap;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
@ -33,6 +32,7 @@ import org.junit.FixMethodOrder;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.federation.kerberos.KerberosFederationProvider; import org.keycloak.federation.kerberos.KerberosFederationProvider;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
@ -41,18 +41,17 @@ import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.representations.account.UserRepresentation; import org.keycloak.representations.account.UserRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.account.AccountCredentialResource; import org.keycloak.services.resources.account.AccountCredentialResource;
import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.LDAPRule; import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.testsuite.util.LDAPTestUtils; import org.keycloak.testsuite.util.LDAPTestUtils;
import org.keycloak.testsuite.util.TokenUtil; import org.keycloak.testsuite.util.TokenUtil;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -115,56 +114,48 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
public void testUpdateProfile() throws IOException { public void testUpdateProfile() throws IOException {
UserRepresentation user = getProfile(); UserRepresentation user = getProfile();
List<String> origLdapId = new ArrayList<>(user.getAttributes().get(LDAPConstants.LDAP_ID)); // Metadata attributes like LDAP_ID are not present
List<String> origLdapEntryDn = new ArrayList<>(user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN)); Assert.assertNull(user.getAttributes());
Assert.assertEquals(1, origLdapId.size());
Assert.assertEquals(1, origLdapEntryDn.size());
assertThat(user.getAttributes().keySet(), not(contains(KerberosFederationProvider.KERBEROS_PRINCIPAL)));
// Trying to add KERBEROS_PRINCIPAL should fail (Adding attribute, which was not yet present) org.keycloak.representations.idm.UserRepresentation adminRestUserRep = testRealm().users()
.search(user.getUsername()).get(0);
List<String> origLdapId = adminRestUserRep.getAttributes().get(LDAPConstants.LDAP_ID);
List<String> origLdapEntryDn = adminRestUserRep.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN);
Assert.assertNotNull(origLdapId.get(0));
Assert.assertNotNull(origLdapEntryDn.get(0));
// Trying to add KERBEROS_PRINCIPAL (Adding attribute, which was not yet present). Request does not fail, but attribute is not updated
user.setFirstName("JohnUpdated"); user.setFirstName("JohnUpdated");
user.setLastName("DoeUpdated"); user.setLastName("DoeUpdated");
user.singleAttribute(KerberosFederationProvider.KERBEROS_PRINCIPAL, "foo"); user.singleAttribute(KerberosFederationProvider.KERBEROS_PRINCIPAL, "foo");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED); updateProfileExpectSuccess(user);
user = getProfile();
Assert.assertEquals("JohnUpdated", user.getFirstName());
Assert.assertEquals("DoeUpdated", user.getLastName());
Assert.assertNull(user.getAttributes());
// The same test, but consider case sensitivity // Trying to update LDAP_ID should fail (Updating existing attribute, which is present on the user even if not visible to the user)
user.getAttributes().remove(KerberosFederationProvider.KERBEROS_PRINCIPAL); user.singleAttribute(LDAPConstants.LDAP_ID, "123");
user.singleAttribute("KERberos_principal", "foo");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Trying to update LDAP_ID should fail (Updating existing attribute, which was present)
user.getAttributes().remove("KERberos_principal");
user.setFirstName("JohnUpdated");
user.setLastName("DoeUpdated");
user.getAttributes().get(LDAPConstants.LDAP_ID).remove(0);
user.getAttributes().get(LDAPConstants.LDAP_ID).add("123");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Trying to delete LDAP_ID should fail (Removing attribute, which was present here already)
user.getAttributes().get(LDAPConstants.LDAP_ID).remove(0);
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED); updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// ignore removal for read-only attributes // ignore removal for read-only attributes
user.getAttributes().remove(LDAPConstants.LDAP_ID); user.getAttributes().remove(LDAPConstants.LDAP_ID);
updateProfileExpectSuccess(user); updateProfileExpectSuccess(user);
user = getProfile(); user = getProfile();
assertFalse(user.getAttributes().get(LDAPConstants.LDAP_ID).isEmpty()); Assert.assertNull(user.getAttributes());
// Trying to update LDAP_ENTRY_DN should fail // Trying to update LDAP_ENTRY_DN should fail
user.getAttributes().put(LDAPConstants.LDAP_ID, origLdapId); user.singleAttribute(LDAPConstants.LDAP_ENTRY_DN, "ou=foo,dc=bar");
user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN).remove(0);
user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN).add("ou=foo,dc=bar");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED); updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Update firstName and lastName should be fine // Update firstName and lastName should be fine
user.getAttributes().put(LDAPConstants.LDAP_ENTRY_DN, origLdapEntryDn); user.getAttributes().remove(LDAPConstants.LDAP_ENTRY_DN);
updateProfileExpectSuccess(user); updateProfileExpectSuccess(user);
user = getProfile(); user = getProfile();
assertEquals("JohnUpdated", user.getFirstName()); assertEquals("JohnUpdated", user.getFirstName());
assertEquals("DoeUpdated", user.getLastName()); assertEquals("DoeUpdated", user.getLastName());
assertEquals(origLdapId, user.getAttributes().get(LDAPConstants.LDAP_ID)); Assert.assertNull(user.getAttributes());
assertEquals(origLdapEntryDn, user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN));
// Revert // Revert
user.setFirstName("John"); user.setFirstName("John");
@ -172,6 +163,67 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
updateProfileExpectSuccess(user); updateProfileExpectSuccess(user);
} }
@Test
public void testUpdateProfileUnmanagedAttributes() throws IOException {
// User profile unmanaged attributes supported
UserProfileResource userProfileRes = testRealm().users().userProfile();
UPConfig origConfig = VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
try {
UserRepresentation user = getProfile();
// Metadata attributes like LDAP_ID are not present
Assert.assertNull(user.getAttributes());
org.keycloak.representations.idm.UserRepresentation adminRestUserRep = testRealm().users()
.search(user.getUsername()).get(0);
List<String> origLdapId = adminRestUserRep.getAttributes().get(LDAPConstants.LDAP_ID);
List<String> origLdapEntryDn = adminRestUserRep.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN);
Assert.assertNotNull(origLdapId.get(0));
Assert.assertNotNull(origLdapEntryDn.get(0));
// Trying to add KERBEROS_PRINCIPAL should fail (Adding attribute, which was not yet present)
user.setFirstName("JohnUpdated");
user.setLastName("DoeUpdated");
user.singleAttribute(KerberosFederationProvider.KERBEROS_PRINCIPAL, "foo");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// The same test, but consider case sensitivity
user.getAttributes().remove(KerberosFederationProvider.KERBEROS_PRINCIPAL);
user.singleAttribute("KERberos_principal", "foo");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Trying to update LDAP_ID should fail (Updating existing attribute, which was present)
user.getAttributes().remove("KERberos_principal");
user.setFirstName("JohnUpdated");
user.setLastName("DoeUpdated");
user.singleAttribute(LDAPConstants.LDAP_ID, "123");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Trying to delete LDAP_ID (by set to null) should fail (Removing attribute, which was present here already)
user.singleAttribute(LDAPConstants.LDAP_ID, null);
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// ignore removal for read-only attributes
user.getAttributes().remove(LDAPConstants.LDAP_ID);
updateProfileExpectSuccess(user);
user = getProfile();
Assert.assertNull(user.getAttributes());
user = getProfile();
assertEquals("JohnUpdated", user.getFirstName());
assertEquals("DoeUpdated", user.getLastName());
Assert.assertNull(user.getAttributes());
// Revert
user.setFirstName("John");
user.setLastName("Doe");
updateProfileExpectSuccess(user);
} finally {
userProfileRes.update(origConfig);
}
}
@Test @Test
public void testGetCredentials() throws IOException { public void testGetCredentials() throws IOException {
List<AccountCredentialResource.CredentialContainer> credentials = getCredentials(); List<AccountCredentialResource.CredentialContainer> credentials = getCredentials();
@ -231,7 +283,8 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
assertEquals("john-alias@email.org", usernew.getEmail()); assertEquals("john-alias@email.org", usernew.getEmail());
assertFalse(usernew.isEmailVerified()); assertFalse(usernew.isEmailVerified());
usernew.getAttributes().clear(); // No metadata attributes like LDAP_ID or LDAP_ENTRY_DN present in account REST API
Assert.assertNull(usernew.getAttributes());
//clean up //clean up
usernew.setEmail("john@email.org"); usernew.setEmail("john@email.org");
@ -240,13 +293,22 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
org.keycloak.representations.idm.UserRepresentation userRep = testRealm().users() org.keycloak.representations.idm.UserRepresentation userRep = testRealm().users()
.search(usernew.getUsername()).get(0); .search(usernew.getUsername()).get(0);
// Metadata attributes present in admin REST API
assertTrue(userRep.getAttributes().containsKey(LDAPConstants.LDAP_ID));
assertTrue(userRep.getAttributes().containsKey(LDAPConstants.LDAP_ENTRY_DN));
userRep.setAttributes(null); userRep.setAttributes(null);
testRealm().users().get(userRep.getId()).update(userRep); testRealm().users().get(userRep.getId()).update(userRep);
usernew = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class); usernew = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
assertTrue(usernew.getAttributes().containsKey(LDAPConstants.LDAP_ID)); // Metadata attributes still not present in account REST
assertTrue(usernew.getAttributes().containsKey(LDAPConstants.LDAP_ENTRY_DN)); Assert.assertNull(usernew.getAttributes());
// Metadata attributes still present in admin REST API
userRep = testRealm().users().search(usernew.getUsername()).get(0);
assertTrue(userRep.getAttributes().containsKey(LDAPConstants.LDAP_ID));
assertTrue(userRep.getAttributes().containsKey(LDAPConstants.LDAP_ENTRY_DN));
} }

View file

@ -18,7 +18,9 @@
package org.keycloak.testsuite.federation.ldap; package org.keycloak.testsuite.federation.ldap;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
@ -36,7 +38,11 @@ import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation; import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
@ -48,6 +54,10 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.forms.VerifyProfileTest.setUserProfileConfiguration;
import static org.keycloak.util.JsonSerialization.writeValueAsString;
/** /**
* *
@ -80,6 +90,10 @@ public class LDAPAdminRestApiTest extends AbstractLDAPTest {
LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1"); LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
}); });
UPConfig cfg = testRealm().users().userProfile().getConfiguration();
cfg.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
testRealm().users().userProfile().update(cfg);
} }
@Test @Test
@ -245,4 +259,75 @@ public class LDAPAdminRestApiTest extends AbstractLDAPTest {
assertEquals("unknown_error", error.getError()); assertEquals("unknown_error", error.getError());
} }
} }
@Test
public void testUpdateReadOnlyAttributeWhenNotSetToUser() throws Exception {
RealmRepresentation realmRep = testRealm().toRepresentation();
enableSyncRegistration(realmRep, Boolean.FALSE);
UserRepresentation newUser = UserBuilder.create()
.username("admintestuser1")
.password("userpass")
.addAttribute("foo", "foo-value")
.enabled(true)
.build();
UPConfig origUpConfig = testRealm().users().userProfile().getConfiguration();
try (Response response = testRealm().users().create(newUser)) {
enableDynamicUserProfileConfig();
String newUserId = ApiUtil.getCreatedId(response);
getCleanup().addUserId(newUserId);
UserResource user = testRealm().users().get(newUserId);
UserRepresentation userRep = user.toRepresentation();
assertNull(userRep.getAttributes());
userRep.singleAttribute(LDAPConstants.LDAP_ID, "");
user.update(userRep);
userRep = testRealm().users().get(newUserId).toRepresentation();
assertNull(userRep.getAttributes());
userRep.singleAttribute(LDAPConstants.LDAP_ID, null);
user.update(userRep);
userRep = testRealm().users().get(newUserId).toRepresentation();
assertNull(userRep.getAttributes());
try {
userRep.singleAttribute(LDAPConstants.LDAP_ID, "should-fail");
user.update(userRep);
fail("Should fail, attribute is read-only");
} catch (BadRequestException ignore) {
}
} finally {
enableSyncRegistration(realmRep, Boolean.TRUE);
testRealm().users().userProfile().update(origUpConfig);
}
}
private void enableDynamicUserProfileConfig() throws IOException {
UPConfig upConfig = testRealm().users().userProfile().getConfiguration();
upConfig.setUnmanagedAttributePolicy(null);
UPAttribute attribute = new UPAttribute();
attribute.setName(LDAPConstants.LDAP_ID);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setView(Collections.singleton("admin"));
attribute.setPermissions(permissions);
upConfig.addOrReplaceAttribute(attribute);
setUserProfileConfiguration(testRealm(), writeValueAsString(upConfig));
}
private void enableSyncRegistration(RealmRepresentation realmRep, Boolean aFalse) {
ComponentRepresentation ldapStorage = testRealm().components()
.query(realmRep.getId(), UserStorageProvider.class.getName()).get(0);
ldapStorage.getConfig().put(LDAPConstants.SYNC_REGISTRATIONS, Collections.singletonList(aFalse.toString()));
testRealm().components().component(ldapStorage.getId()).update(ldapStorage);
}
} }

View file

@ -1,126 +0,0 @@
/*
* Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.testsuite.federation.ldap;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.forms.VerifyProfileTest.disableDynamicUserProfile;
import static org.keycloak.testsuite.forms.VerifyProfileTest.setUserProfileConfiguration;
import static org.keycloak.util.JsonSerialization.writeValueAsString;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.util.Collections;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.models.LDAPConstants;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPAdminRestApiWithUserProfileTest extends LDAPAdminRestApiTest {
@Test
public void testUpdateReadOnlyAttributeWhenNotSetToUser() throws Exception {
RealmRepresentation realmRep = testRealm().toRepresentation();
enableSyncRegistration(realmRep, Boolean.FALSE);
UserRepresentation newUser = UserBuilder.create()
.username("admintestuser1")
.password("userpass")
.addAttribute("foo", "foo-value")
.enabled(true)
.build();
try (Response response = testRealm().users().create(newUser)) {
enableDynamicUserProfile(realmRep);
String newUserId = ApiUtil.getCreatedId(response);
getCleanup().addUserId(newUserId);
UserResource user = testRealm().users().get(newUserId);
UserRepresentation userRep = user.toRepresentation();
assertNull(userRep.getAttributes());
userRep.singleAttribute(LDAPConstants.LDAP_ID, "");
user.update(userRep);
userRep = testRealm().users().get(newUserId).toRepresentation();
assertNull(userRep.getAttributes());
userRep.singleAttribute(LDAPConstants.LDAP_ID, null);
user.update(userRep);
userRep = testRealm().users().get(newUserId).toRepresentation();
assertNull(userRep.getAttributes());
try {
userRep.singleAttribute(LDAPConstants.LDAP_ID, "should-fail");
user.update(userRep);
fail("Should fail, attribute is read-only");
} catch (BadRequestException ignore) {
}
} finally {
disableDynamicUserProfile(testRealm());
enableSyncRegistration(realmRep, Boolean.TRUE);
}
}
private void enableDynamicUserProfile(RealmRepresentation realmRep) throws IOException {
VerifyProfileTest.enableDynamicUserProfile(realmRep);
testRealm().update(realmRep);
UPConfig upConfig = testRealm().users().userProfile().getConfiguration();
UPAttribute attribute = new UPAttribute();
attribute.setName(LDAPConstants.LDAP_ID);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setView(Collections.singleton("admin"));
attribute.setPermissions(permissions);
upConfig.addOrReplaceAttribute(attribute);
setUserProfileConfiguration(testRealm(), writeValueAsString(upConfig));
}
private void enableSyncRegistration(RealmRepresentation realmRep, Boolean aFalse) {
ComponentRepresentation ldapStorage = testRealm().components()
.query(realmRep.getId(), UserStorageProvider.class.getName()).get(0);
ldapStorage.getConfig().put(LDAPConstants.SYNC_REGISTRATIONS, Collections.singletonList(aFalse.toString()));
testRealm().components().component(ldapStorage.getId()).update(ldapStorage);
}
}

View file

@ -23,6 +23,7 @@ import org.junit.ClassRule;
import org.junit.FixMethodOrder; import org.junit.FixMethodOrder;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException; import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
@ -38,6 +39,7 @@ import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper;
import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.LDAPRule; import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.testsuite.util.LDAPTestUtils; import org.keycloak.testsuite.util.LDAPTestUtils;
@ -85,6 +87,10 @@ public class LDAPBinaryAttributesTest extends AbstractLDAPTest {
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true); appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}); });
// User profile unmanaged attributes supported
UserProfileResource userProfileRes = testRealm().users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
} }

View file

@ -29,11 +29,9 @@ import org.junit.FixMethodOrder;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata; import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute; import org.keycloak.representations.userprofile.config.UPAttribute;
@ -42,8 +40,6 @@ import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage; import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.util.LDAPRule; import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.testsuite.util.LDAPTestUtils; import org.keycloak.testsuite.util.LDAPTestUtils;
@ -56,7 +52,6 @@ import static org.keycloak.userprofile.UserProfileUtil.USER_METADATA_GROUP;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class LDAPUserProfileTest extends AbstractLDAPTest { public class LDAPUserProfileTest extends AbstractLDAPTest {
@ClassRule @ClassRule
@ -91,10 +86,6 @@ public class LDAPUserProfileTest extends AbstractLDAPTest {
LDAPObject john2 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak2", "John", "Doe", "john2@email.org", null, "1234"); LDAPObject john2 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak2", "John", "Doe", "john2@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john2, "Password1"); LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john2, "Password1");
}); });
RealmRepresentation realm = testRealm().toRepresentation();
VerifyProfileTest.enableDynamicUserProfile(realm);
testRealm().update(realm);
} }
@Test @Test

View file

@ -26,11 +26,13 @@ import jakarta.ws.rs.core.Response;
import java.util.Map; import java.util.Map;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.FixMethodOrder; import org.junit.FixMethodOrder;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.ComponentResource; import org.keycloak.admin.client.resource.ComponentResource;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
@ -51,6 +53,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.federation.ldap.LDAPProvidersIntegrationTest; import org.keycloak.testsuite.federation.ldap.LDAPProvidersIntegrationTest;
import org.keycloak.testsuite.federation.ldap.LDAPTestAsserts; import org.keycloak.testsuite.federation.ldap.LDAPTestAsserts;
import org.keycloak.testsuite.federation.ldap.LDAPTestContext; import org.keycloak.testsuite.federation.ldap.LDAPTestContext;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.LDAPTestUtils; import org.keycloak.testsuite.util.LDAPTestUtils;
@ -66,6 +69,12 @@ public class LDAPProvidersIntegrationNoImportTest extends LDAPProvidersIntegrati
return false; return false;
} }
@Before
public void enableUserProfileUnmanagedAttributes() {
UserProfileResource userProfileRes = testRealm().users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
}
@Override @Override
protected void assertFederatedUserLink(UserRepresentation user) { protected void assertFederatedUserLink(UserRepresentation user) {

View file

@ -11,6 +11,7 @@ import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.ObjectUtil;
@ -44,6 +45,7 @@ import org.keycloak.testsuite.arquillian.annotation.ModelTest;
import org.keycloak.testsuite.federation.UserMapStorage; import org.keycloak.testsuite.federation.UserMapStorage;
import org.keycloak.testsuite.federation.UserMapStorageFactory; import org.keycloak.testsuite.federation.UserMapStorageFactory;
import org.keycloak.testsuite.federation.UserPropertyFileStorageFactory; import org.keycloak.testsuite.federation.UserPropertyFileStorageFactory;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.RegisterPage;
@ -160,6 +162,9 @@ public class UserStorageTest extends AbstractAuthTest {
propProviderRWId = addComponent(newPropProviderRW()); propProviderRWId = addComponent(newPropProviderRW());
createAppClientInRealm(testRealmResource().toRepresentation().getRealm()); createAppClientInRealm(testRealmResource().toRepresentation().getRealm());
UserProfileResource userProfileRes = testRealmResource().users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
} }
@After @After

View file

@ -1,8 +1,10 @@
package org.keycloak.testsuite.forms; package org.keycloak.testsuite.forms;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.authentication.authenticators.access.AllowAccessAuthenticatorFactory; import org.keycloak.authentication.authenticators.access.AllowAccessAuthenticatorFactory;
import org.keycloak.authentication.authenticators.access.DenyAccessAuthenticatorFactory; import org.keycloak.authentication.authenticators.access.DenyAccessAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.PasswordFormFactory; import org.keycloak.authentication.authenticators.browser.PasswordFormFactory;
@ -63,6 +65,12 @@ public class ConditionalUserAttributeAuthenticatorTest extends AbstractTestRealm
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) {} public void configureTestRealm(RealmRepresentation testRealm) {}
@Before
public void configureUserProfile() {
UserProfileResource userProfileRes = testRealm().users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
}
private void createUsers() { private void createUsers() {
GroupRepresentation subGroup = GroupBuilder.create().name(SUBGROUP).build(); GroupRepresentation subGroup = GroupBuilder.create().name(SUBGROUP).build();
testRealm().groups().add(subGroup); testRealm().groups().add(subGroup);

View file

@ -56,6 +56,8 @@ import org.keycloak.testsuite.util.AccountHelper;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.keycloak.testsuite.util.WaitUtils;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
@ -611,7 +613,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
} }
} }
protected UserRepresentation getUser(String userId) { private UserRepresentation getUser(String userId) {
return testRealm().users().get(userId).toRepresentation(); return testRealm().users().get(userId).toRepresentation();
} }

View file

@ -34,26 +34,34 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
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.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.util.ClientScopeBuilder; import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.KeycloakModelUtils; import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
/** /**
* Test user registration with customized user-profile configurations
*
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE) public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
public class RegisterWithUserProfileTest extends RegisterTest {
private static final String SCOPE_LAST_NAME = "lastName"; private static final String SCOPE_LAST_NAME = "lastName";
@ -63,13 +71,26 @@ public class RegisterWithUserProfileTest extends RegisterTest {
public static String UP_CONFIG_BASIC_ATTRIBUTES = "{\"name\": \"username\"," + PERMISSIONS_ALL + ", \"required\": {}}," public static String UP_CONFIG_BASIC_ATTRIBUTES = "{\"name\": \"username\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"email\"," + PERMISSIONS_ALL + ", \"required\": {\"roles\" : [\"user\"]}},"; + "{\"name\": \"email\"," + PERMISSIONS_ALL + ", \"required\": {\"roles\" : [\"user\"]}},";
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected AppPage appPage;
@Page
protected LoginPage loginPage;
@Page
protected ErrorPage errorPage;
@Page
protected RegisterPage registerPage;
@Rule
public GreenMailRule greenMail = new GreenMailRule();
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
testRealm.setClientScopes(new ArrayList<>()); testRealm.setClientScopes(new ArrayList<>());
testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_LAST_NAME).protocol("openid-connect").build()); testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_LAST_NAME).protocol("openid-connect").build());
testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_DEPARTMENT).protocol("openid-connect").build()); testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_DEPARTMENT).protocol("openid-connect").build());
@ -683,7 +704,11 @@ public class RegisterWithUserProfileTest extends RegisterTest {
} }
} }
protected void setUserProfileConfiguration(String configuration) { private void setUserProfileConfiguration(String configuration) {
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration); VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
} }
private UserRepresentation getUser(String userId) {
return testRealm().users().get(userId).toRepresentation();
}
} }

View file

@ -21,14 +21,12 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN; import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER; import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -41,6 +39,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource;
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;
@ -79,7 +78,6 @@ import org.openqa.selenium.By;
/** /**
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class VerifyProfileTest extends AbstractTestRealmKeycloakTest { public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
public static final String SCOPE_DEPARTMENT = "department"; public static final String SCOPE_DEPARTMENT = "department";
@ -117,9 +115,6 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
enableDynamicUserProfile(testRealm);
UserRepresentation user = UserBuilder.create().id(UUID.randomUUID().toString()).username("login-test").email("login@test.com").enabled(true).password("password").build(); UserRepresentation user = UserBuilder.create().id(UUID.randomUUID().toString()).username("login-test").email("login@test.com").enabled(true).password("password").build();
UserRepresentation user2 = UserBuilder.create().id(UUID.randomUUID().toString()).username("login-test2").email("login2@test.com").enabled(true).password("password").build(); UserRepresentation user2 = UserBuilder.create().id(UUID.randomUUID().toString()).username("login-test2").email("login2@test.com").enabled(true).password("password").build();
UserRepresentation user3 = UserBuilder.create().id(UUID.randomUUID().toString()).username("login-test3").email("login3@test.com").enabled(true).password("password").lastName("ExistingLast").build(); UserRepresentation user3 = UserBuilder.create().id(UUID.randomUUID().toString()).username("login-test3").email("login3@test.com").enabled(true).password("password").lastName("ExistingLast").build();
@ -404,15 +399,8 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
@Test @Test
public void testIgnoreCustomAttributeWhenUserProfileIsDisabled() { public void testIgnoreCustomAttributeWhenUserProfileIsDisabled() {
try { testingClient.server(TEST_REALM_NAME).run(setEmptyFirstNameAndCustomAttribute());
disableDynamicUserProfile(testRealm()); testDefaultProfile();
testingClient.server(TEST_REALM_NAME).run(setEmptyFirstNameAndCustomAttribute());
testDefaultProfile();
} finally {
RealmRepresentation realm = testRealm().toRepresentation();
enableDynamicUserProfile(realm);
testRealm().update(realm);
}
} }
private static RunOnServer setEmptyFirstNameAndCustomAttribute() { private static RunOnServer setEmptyFirstNameAndCustomAttribute() {
@ -1166,7 +1154,7 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
} }
@Test @Test
public void testConfigurationRemainsAfterReset() throws IOException { public void testConfigurationPersisted() throws IOException {
String customConfig = "{\"attributes\": [" String customConfig = "{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "}," + "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
@ -1175,13 +1163,7 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
UPConfig persistedConfig = setUserProfileConfiguration(customConfig); UPConfig persistedConfig = setUserProfileConfiguration(customConfig);
RealmResource realmRes = testRealm(); JsonTestUtils.assertJsonEquals(JsonSerialization.writeValueAsString(persistedConfig), testRealm().users().userProfile().getConfiguration());
disableDynamicUserProfile(realmRes, false);
RealmRepresentation realm = realmRes.toRepresentation();
enableDynamicUserProfile(realm);
testRealm().update(realm);
JsonTestUtils.assertJsonEquals(JsonSerialization.writeValueAsString(persistedConfig), realmRes.users().userProfile().getConfiguration());
} }
protected UserRepresentation getUser(String userId) { protected UserRepresentation getUser(String userId) {
@ -1211,30 +1193,6 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
return result; return result;
} }
public static void enableDynamicUserProfile(RealmRepresentation testRealm) {
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
}
public static void disableDynamicUserProfile(RealmResource realm) {
disableDynamicUserProfile(realm, true);
}
public static void disableDynamicUserProfile(RealmResource realm, boolean reset) {
RealmRepresentation realmRep = realm.toRepresentation();
if (realmRep.getAttributes() == null) {
realmRep.setAttributes(new HashMap<>());
}
realmRep.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.FALSE.toString());
realm.update(realmRep);
if (reset) {
setUserProfileConfiguration(realm, null);
}
}
public static UPConfig setUserProfileConfiguration(RealmResource testRealm, String configuration) { public static UPConfig setUserProfileConfiguration(RealmResource testRealm, String configuration) {
try { try {
UPConfig config = configuration == null ? null : JsonSerialization.readValue(configuration, UPConfig.class); UPConfig config = configuration == null ? null : JsonSerialization.readValue(configuration, UPConfig.class);
@ -1261,6 +1219,13 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
} }
} }
public static UPConfig enableUnmanagedAttributes(UserProfileResource upResource) {
UPConfig cfg = upResource.getConfiguration();
cfg.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
upResource.update(cfg);
return cfg;
}
public static UserRepresentation getUser(RealmResource testRealm, String userId) { public static UserRepresentation getUser(RealmResource testRealm, String userId) {
return testRealm.users().get(userId).toRepresentation(); return testRealm.users().get(userId).toRepresentation();
} }

View file

@ -1,28 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.i18n;
import org.keycloak.common.Profile.Feature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
/**
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class LoginPageWithUserProfileTest extends LoginPageTest {
}

View file

@ -106,13 +106,13 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.migration.migrators.MigrateTo24_0_0.REALM_USER_PROFILE_ENABLED;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT; import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS; import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
import static org.keycloak.models.AccountRoles.VIEW_GROUPS; import static org.keycloak.models.AccountRoles.VIEW_GROUPS;
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID; import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
import static org.keycloak.testsuite.Assert.assertNames; import static org.keycloak.testsuite.Assert.assertNames;
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.UP_COMPONENT_CONFIG_KEY; import static org.keycloak.userprofile.DeclarativeUserProfileProvider.UP_COMPONENT_CONFIG_KEY;
/** /**
@ -1204,7 +1204,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
RealmRepresentation rep = realm.toRepresentation(); RealmRepresentation rep = realm.toRepresentation();
Map<String, String> attributes = rep.getAttributes(); Map<String, String> attributes = rep.getAttributes();
String userProfileEnabled = attributes.get(REALM_USER_PROFILE_ENABLED); String userProfileEnabled = attributes.get(REALM_USER_PROFILE_ENABLED);
assertTrue(Boolean.parseBoolean(userProfileEnabled)); assertNull(userProfileEnabled);
} }
private void testUnmanagedAttributePolicySet(RealmResource realm, UnmanagedAttributePolicy policy) { private void testUnmanagedAttributePolicySet(RealmResource realm, UnmanagedAttributePolicy policy) {

View file

@ -17,10 +17,8 @@
package org.keycloak.testsuite.migration; package org.keycloak.testsuite.migration;
import org.junit.Test; import org.junit.Test;
import org.keycloak.common.Profile.Feature;
import org.keycloak.exportimport.util.ImportUtils; import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.utils.io.IOUtil; import org.keycloak.testsuite.utils.io.IOUtil;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -31,7 +29,6 @@ import java.util.Map;
/** /**
* Tests that we can import json file from previous version. MigrationTest only tests DB. * Tests that we can import json file from previous version. MigrationTest only tests DB.
*/ */
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class JsonFileImport1903MigrationTest extends AbstractJsonFileImportMigrationTest { public class JsonFileImport1903MigrationTest extends AbstractJsonFileImportMigrationTest {
@Override @Override

View file

@ -19,9 +19,7 @@ package org.keycloak.testsuite.migration;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.migration.Migration; import org.keycloak.testsuite.arquillian.migration.Migration;
import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotFoundException;
@ -34,7 +32,6 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
* *
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a> * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/ */
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class MigrationTest extends AbstractMigrationTest { public class MigrationTest extends AbstractMigrationTest {
@Override @Override

View file

@ -139,7 +139,6 @@ public class ImportTest extends AbstractTestRealmKeycloakTest {
} }
@Test @Test
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public void importUserProfile() throws Exception { public void importUserProfile() throws Exception {
final String realmString = IOUtils.toString(getClass().getResourceAsStream("/model/import-userprofile.json"), StandardCharsets.UTF_8); final String realmString = IOUtils.toString(getClass().getResourceAsStream("/model/import-userprofile.json"), StandardCharsets.UTF_8);

View file

@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientScopeResource; import org.keycloak.admin.client.resource.ClientScopeResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource; import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
@ -47,11 +48,13 @@ import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.ProtocolMappersUpdater; import org.keycloak.testsuite.updaters.ProtocolMappersUpdater;
import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.AdminClientUtil;
@ -120,6 +123,10 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
* @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored() * @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
*/ */
oauth.clientId("test-app"); oauth.clientId("test-app");
// enable user profile unmanaged attributes
UserProfileResource upResource = adminClient.realm("test").users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(upResource);
} }

View file

@ -29,7 +29,6 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCConfigAttributes;
@ -38,8 +37,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
@ -47,7 +44,6 @@ import org.keycloak.testsuite.util.UserBuilder;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public class ServiceAccountUserProfileTest extends AbstractKeycloakTest { public class ServiceAccountUserProfileTest extends AbstractKeycloakTest {
private static String userId; private static String userId;
@ -116,7 +112,6 @@ public class ServiceAccountUserProfileTest extends AbstractKeycloakTest {
realm.user(serviceAccountUser); realm.user(serviceAccountUser);
RealmRepresentation realmRep = realm.build(); RealmRepresentation realmRep = realm.build();
VerifyProfileTest.enableDynamicUserProfile(realmRep);
testRealms.add(realmRep); testRealms.add(realmRep);
} }

View file

@ -128,7 +128,7 @@ public class LogoutTest extends AbstractSamlTest {
.targetAttributeSamlResponse() .targetAttributeSamlResponse()
.targetUri(getSamlBrokerUrl(REALM_NAME)) .targetUri(getSamlBrokerUrl(REALM_NAME))
.build() .build()
.updateProfile().username("a").email("a@b.c").firstName("A").lastName("B").build() .updateProfile().username("aaa").email("a@b.c").firstName("A").lastName("B").build()
.followOneRedirect() .followOneRedirect()
// Now returning back to the app // Now returning back to the app

View file

@ -22,15 +22,23 @@ package org.keycloak.testsuite.theme;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
@ -52,12 +60,19 @@ public class CustomRegistrationTemplateTest extends AbstractTestRealmKeycloakTes
@Page @Page
protected AppPage appPage; protected AppPage appPage;
private UPConfig upConfig;
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.setRegistrationAllowed(true); testRealm.setRegistrationAllowed(true);
testRealm.setLoginTheme("address"); testRealm.setLoginTheme("address");
} }
@Before
public void onBefore() {
upConfig = updateUserProfileConfiguration();
}
@Test @Test
public void testRegistration() { public void testRegistration() {
//contains few special characters we want to be sure they are allowed in username //contains few special characters we want to be sure they are allowed in username
@ -67,6 +82,50 @@ public class CustomRegistrationTemplateTest extends AbstractTestRealmKeycloakTes
assertCustomAttributes(attributes); assertCustomAttributes(attributes);
} }
@Test
public void testUnmanagedAttributeEnabled() {
upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = register();
assertCustomAttributes(user.getAttributes());
}
@Test
public void testUnmanagedAttributeAdminEdit() {
upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = register();
assertNull(user.getAttributes());
}
@Test
public void testUnmanagedAttributeDisabled() {
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = register();
assertNull(user.getAttributes());
}
private UPConfig updateUserProfileConfiguration() {
UPConfig upCOnfig = testRealm().users().userProfile().getConfiguration();
upCOnfig.setUnmanagedAttributePolicy(null);
upCOnfig.addOrReplaceAttribute(new UPAttribute("street", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("locality", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("region", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("postal_code", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("country", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
testRealm().users().userProfile().update(upCOnfig);
return upCOnfig;
}
protected static void assertCustomAttributes(Map<String, List<String>> attributes) { protected static void assertCustomAttributes(Map<String, List<String>> attributes) {
for (Entry<String, String> attribute : CUSTOM_ATTRIBUTES.entrySet()) { for (Entry<String, String> attribute : CUSTOM_ATTRIBUTES.entrySet()) {
String name = attribute.getKey(); String name = attribute.getKey();
@ -93,9 +152,6 @@ public class CustomRegistrationTemplateTest extends AbstractTestRealmKeycloakTes
} }
private void navigateToRegistrationPage() { private void navigateToRegistrationPage() {
RealmRepresentation realm = testRealm().toRepresentation();
realm.setRegistrationAllowed(true);
testRealm().update(realm);
loginPage.open(); loginPage.open();
loginPage.clickRegister(); loginPage.clickRegister();
} }

View file

@ -1,111 +0,0 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.testsuite.theme;
import static org.junit.Assert.assertNull;
import static org.keycloak.testsuite.forms.VerifyProfileTest.disableDynamicUserProfile;
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.Profile.Feature;
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.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class CustomRegistrationTemplateUserProfileTest extends CustomRegistrationTemplateTest {
private UPConfig upConfig;
@Before
public void onBefore() {
upConfig = updateUserProfileConfiguration();
}
@After
public void onAfter() {
disableDynamicUserProfile(testRealm());
}
@Override
@Test
public void testRegistration() {
upConfig.setUnmanagedAttributePolicy(null);
testRealm().users().userProfile().update(upConfig);
super.testRegistration();
}
@Test
public void testUnmanagedAttributeEnabled() {
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = register();
assertCustomAttributes(user.getAttributes());
}
@Test
public void testUnmanagedAttributeAdminEdit() {
upConfig.setUnmanagedAttributePolicy(null);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = register();
assertNull(user.getAttributes());
}
@Test
public void testUnmanagedAttributeDisabled() {
upConfig.setUnmanagedAttributePolicy(null);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = register();
assertNull(user.getAttributes());
}
private UPConfig updateUserProfileConfiguration() {
RealmRepresentation realm = testRealm().toRepresentation();
enableDynamicUserProfile(realm);
testRealm().update(realm);
UPConfig upCOnfig = testRealm().users().userProfile().getConfiguration();
upCOnfig.addOrReplaceAttribute(new UPAttribute("street", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("locality", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("region", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("postal_code", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("country", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
testRealm().users().userProfile().update(upCOnfig);
return upCOnfig;
}
}

View file

@ -23,15 +23,18 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.ws.rs.core.Response;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -39,7 +42,11 @@ import org.keycloak.models.Constants;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage; import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
@ -63,26 +70,31 @@ public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTe
@Page @Page
protected AppPage appPage; protected AppPage appPage;
private UPConfig upConfig;
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.setLoginTheme("address"); testRealm.setLoginTheme("address");
// the custom theme expects email as username and the username field is not rendered at all // the custom theme expects email as username and the username field is not rendered at all
testRealm.setRegistrationEmailAsUsername(true); testRealm.setRegistrationEmailAsUsername(true);
}
@Before
public void onBefore() {
UserRepresentation user = UserBuilder.create().enabled(true) UserRepresentation user = UserBuilder.create().enabled(true)
.username("tom") .username("tom")
.email("tom@keycloak.org") .email("tom@keycloak.org")
.password("password") .password("password")
.firstName("Tom") .firstName("Tom")
.lastName("Brady").build(); .lastName("Brady")
testRealm.getUsers().add(user); .requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.name())
} .build();
Response resp = testRealm().users().create(user);
String userId = ApiUtil.getCreatedId(resp);
resp.close();
getCleanup().addUserId(userId);
@Before upConfig = updateUserProfileConfiguration();
public void onBefore() {
UserRepresentation user = getUser("tom");
user.setAttributes(Map.of());
user.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PROFILE.name()));
testRealm().users().get(user.getId()).update(user);
} }
@Test @Test
@ -94,6 +106,37 @@ public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTe
assertCustomAttributes(user.getAttributes()); assertCustomAttributes(user.getAttributes());
} }
@Test
public void testUnmanagedAttributeEnabled() {
upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
testUpdateProfile();
}
@Test
public void testUnmanagedAttributeAdminEdit() {
upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = updateProfile();
assertNull(user.getAttributes());
}
@Test
public void testUnmanagedAttributeDisabled() {
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = updateProfile();
assertNull(user.getAttributes());
}
protected UserRepresentation updateProfile() { protected UserRepresentation updateProfile() {
navigateToUpdateProfilePage(); navigateToUpdateProfilePage();
updateProfilePage.update(CUSTOM_ATTRIBUTES.entrySet().stream() updateProfilePage.update(CUSTOM_ATTRIBUTES.entrySet().stream()
@ -124,4 +167,16 @@ public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTe
loginPage.login("tom@keycloak.org", "password"); loginPage.login("tom@keycloak.org", "password");
updateProfilePage.assertCurrent(); updateProfilePage.assertCurrent();
} }
private UPConfig updateUserProfileConfiguration() {
UPConfig upCOnfig = testRealm().users().userProfile().getConfiguration();
upCOnfig.setUnmanagedAttributePolicy(null);
upCOnfig.addOrReplaceAttribute(new UPAttribute("street", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("locality", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("region", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("postal_code", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("country", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
testRealm().users().userProfile().update(upCOnfig);
return upCOnfig;
}
} }

View file

@ -1,111 +0,0 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.testsuite.theme;
import static org.junit.Assert.assertNull;
import static org.keycloak.testsuite.forms.VerifyProfileTest.disableDynamicUserProfile;
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.Profile.Feature;
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.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class CustomUpdateProfileTemplateUserProfileTest extends CustomUpdateProfileTemplateTest {
private UPConfig upConfig;
@Before
public void onBefore() {
super.onBefore();
upConfig = updateUserProfileConfiguration();
}
@After
public void onAfter() {
disableDynamicUserProfile(testRealm());
}
@Override
@Test
public void testUpdateProfile() {
upConfig.setUnmanagedAttributePolicy(null);
testRealm().users().userProfile().update(upConfig);
super.testUpdateProfile();
}
@Test
public void testUnmanagedAttributeEnabled() {
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
super.testUpdateProfile();
}
@Test
public void testUnmanagedAttributeAdminEdit() {
upConfig.setUnmanagedAttributePolicy(null);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = updateProfile();
assertNull(user.getAttributes());
}
@Test
public void testUnmanagedAttributeDisabled() {
upConfig.setUnmanagedAttributePolicy(null);
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
upConfig.removeAttribute(name);
}
testRealm().users().userProfile().update(upConfig);
UserRepresentation user = updateProfile();
assertNull(user.getAttributes());
}
private UPConfig updateUserProfileConfiguration() {
RealmRepresentation realm = testRealm().toRepresentation();
enableDynamicUserProfile(realm);
testRealm().update(realm);
UPConfig upCOnfig = testRealm().users().userProfile().getConfiguration();
upCOnfig.addOrReplaceAttribute(new UPAttribute("street", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("locality", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("region", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("postal_code", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
upCOnfig.addOrReplaceAttribute(new UPAttribute("country", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
testRealm().users().userProfile().update(upCOnfig);
return upCOnfig;
}
}

Some files were not shown because too many files have changed in this diff Show more