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
run: |
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:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
@ -297,7 +297,7 @@ jobs:
- name: Start Keycloak server
run: |
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:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin

View file

@ -73,8 +73,6 @@ public class Profile {
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),
CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW),

View file

@ -79,7 +79,6 @@ public class ProfileTest {
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.LINKEDIN_OAUTH
@ -90,7 +89,7 @@ public class ProfileTest {
disabledFeatures.add(Profile.Feature.KERBEROS);
}
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

View file

@ -40,15 +40,8 @@ describe("Realm settings tabs tests", () => {
return this;
};
it("shows the 'user profile' tab if enabled", () => {
it("shows the 'user profile' tab", () => {
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");
});

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 attributeName = "Test";
before(() =>
adminClient.createRealm(realmName, {
attributes: { userProfileEnabled: "true" },
}),
);
before(() => adminClient.createRealm(realmName));
after(() => adminClient.deleteRealm(realmName));
beforeEach(() => {

View file

@ -2,6 +2,7 @@ import { v4 as uuid } from "uuid";
import SidebarPage from "../support/pages/admin-ui/SidebarPage";
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 Masthead from "../support/pages/admin-ui/Masthead";
import ListingPage from "../support/pages/admin-ui/ListingPage";
@ -23,6 +24,7 @@ let groupsList: string[] = [];
describe("User creation", () => {
const loginPage = new LoginPage();
const sidebarPage = new SidebarPage();
const realmSettingsPage = new RealmSettingsPage();
const createUserPage = new CreateUserPage();
const userGroupsPage = new UserGroupsPage();
const masthead = new Masthead();
@ -30,7 +32,7 @@ describe("User creation", () => {
const listingPage = new ListingPage();
const userDetailsPage = new UserDetailsPage();
const credentialsPage = new CredentialsPage();
const attributesTab = new AttributesTab();
const attributesTab = new AttributesTab(true);
const usersPage = new UsersPage();
const identityProviderLinksTab = new IdentityProviderLinksTab();
@ -143,6 +145,14 @@ describe("User creation", () => {
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", () => {
listingPage.goToItemDetails(itemId);

View file

@ -1,11 +1,25 @@
export default class AttributesTab {
#saveAttributeBtn = "save-attributes";
#addAttributeBtn = "attributes-add-row";
#attributesTab = "attributes";
#keyInput = "attributes-key";
#valueInput = "attributes-value";
#removeBtn = "attributes-remove";
#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() {
cy.findByTestId(this.#attributesTab).click();

View file

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

View file

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

View file

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

View file

@ -1461,7 +1461,6 @@ exactSearch=Exact search
value=Value
filenamePlaceholder=Upload a PEM file or paste key below
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.
times.seconds=Seconds
removeMappingTitle=Remove role?
@ -1890,7 +1889,6 @@ moveToGroup=Move {{group1}} to {{group2}}
noRealmRoles=No realm roles
events-disable-confirm=If "Save events" is disabled, subsequent events will not be displayed in the "Events" menu
reqAuthnConstraints=Requested AuthnContext Constraints
userProfileEnabled=User Profile Enabled
eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Pushed authorization request
addIdpMapperNameHelp=Name of the mapper.
requirements.ALTERNATIVE=Alternative

View file

@ -1437,7 +1437,6 @@ exactSearch=Búsqueda exacta
value=Valor
filenamePlaceholder=Cargar un archivo PEM o pegar la clave a continuación
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.
times.seconds=Segundos
removeMappingTitle=¿Eliminar asignación?
@ -1865,7 +1864,6 @@ moveToGroup=Mover {{grupo1}} a {{grupo2}}
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"
reqAuthnConstraints=Restricciones de contexto de autenticación solicitadas
userProfileEnabled=Perfil de usuario habilitado
eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Solicitud de autorización empujada
addIdpMapperNameHelp=Nombre del mapeador.
requirements.ALTERNATIVE=Alternativa

View file

@ -1437,7 +1437,6 @@ exactSearch=Wyszukiwanie dokładne
value=Wartość
filenamePlaceholder=Prześlij plik PEM lub wklej klucz poniżej
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.
times.seconds=Sekundy
removeMappingTitle=Usuń rolę?
@ -1865,7 +1864,6 @@ moveToGroup=Przenieś {{group1}} do {{group2}}
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"
reqAuthnConstraints=Wymagane ograniczenia AuthnContext
userProfileEnabled=Profil użytkownika włączony
eventTypes.PUSHED_AUTHORIZATION_REQUEST.description=Żądanie autoryzacji przesuniętej
addIdpMapperNameHelp=Nazwa mappera.
requirements.ALTERNATIVE=Alternatywa

View file

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

View file

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

View file

@ -417,16 +417,13 @@ export const RealmSettingsTabs = ({
</RoutableTabs>
</Tab>
)}
{isFeatureEnabled(Feature.DeclarativeUserProfile) &&
realm.attributes?.userProfileEnabled === "true" && (
<Tab
title={<TabTitleText>{t("userProfile")}</TabTitleText>}
data-testid="rs-user-profile-tab"
{...userProfileTab}
>
<UserProfileTab />
</Tab>
)}
<Tab
title={<TabTitleText>{t("userProfile")}</TabTitleText>}
data-testid="rs-user-profile-tab"
{...userProfileTab}
>
<UserProfileTab />
</Tab>
<Tab
title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
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 { useRealm } from "../context/realm-context/RealmContext";
import { useFetch } from "../utils/useFetch";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { UserForm } from "./UserForm";
import { UserFormFields, toUserRepresentation } from "./form-state";
import { toUser } from "./routes/User";
@ -27,7 +26,6 @@ export default function CreateUser() {
const { addAlert, addError } = useAlerts();
const navigate = useNavigate();
const { realm: realmName } = useRealm();
const isFeatureEnabled = useIsFeatureEnabled();
const form = useForm<UserFormFields>({ mode: "onChange" });
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
const [realm, setRealm] = useState<RealmRepresentation>();
@ -46,14 +44,7 @@ export default function CreateUser() {
}
setRealm(realm);
const isUserProfileEnabled =
isFeatureEnabled(Feature.DeclarativeUserProfile) &&
realm.attributes?.userProfileEnabled === "true";
setUserProfileMetadata(
isUserProfileEnabled ? userProfileMetadata : undefined,
);
setUserProfileMetadata(userProfileMetadata);
},
[],
);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,7 +40,7 @@ async function startServer() {
[
"start-dev",
"--http-port=8180",
"--features=account3,admin-fine-grained-authz,declarative-user-profile,transient-users",
"--features=account3,admin-fine-grained-authz,transient-users",
...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);
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
public void migrate(KeycloakSession session) {
@ -64,15 +64,15 @@ public class MigrateTo24_0_0 implements Migration {
RealmModel realm = session.getContext().getRealm();
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) {
// 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());
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
// that don't have the declarative user profile enabled
UserProfileProvider provider = session.getProvider(UserProfileProvider.class);

View file

@ -28,7 +28,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
@LegacyStore
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
public void testEnableOnBuild(KeycloakDistribution dist) {
@ -89,7 +89,7 @@ public class FeaturesDistTest {
cliResult.assertStartedDevMode();
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
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
@ -100,7 +100,7 @@ public class FeaturesDistTest {
cliResult.assertStartedDevMode();
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,32 +42,27 @@ public class UserResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Map<String, List<String>> getUnmanagedAttributes() {
RealmModel realm = session.getContext().getRealm();
UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
if (provider.isEnabled(realm)) {
UserProfile profile = provider.create(USER_API, user);
Map<String, List<String>> managedAttributes = profile.getAttributes().getReadable();
Map<String, List<String>> attributes = new HashMap<>(user.getAttributes());
UPConfig upConfig = provider.getConfiguration();
UserProfile profile = provider.create(USER_API, user);
Map<String, List<String>> managedAttributes = profile.getAttributes().getReadable();
Map<String, List<String>> attributes = new HashMap<>(user.getAttributes());
UPConfig upConfig = provider.getConfiguration();
if (upConfig.getUnmanagedAttributePolicy() == null) {
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));
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));
}
}

View file

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

View file

@ -19,7 +19,6 @@ package org.keycloak.userprofile;
import java.util.Map;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
import org.keycloak.representations.userprofile.config.UPConfig;
@ -88,12 +87,4 @@ public interface UserProfileProvider extends Provider {
* @see #getConfiguration()
*/
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.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@ -175,7 +176,12 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
} else if (variable.startsWith("CLAIM.")) {
String name = variable.substring("CLAIM.".length());
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()));
} else {
m.appendReplacement(sb, m.group(1));

View file

@ -385,12 +385,6 @@ public class RealmAdminResource {
if (auth.users().canView()) {
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()) {

View file

@ -41,7 +41,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.userprofile.config.DeclarativeUserProfileModel;
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.representations.userprofile.config.UPGroup;
import org.keycloak.userprofile.validator.AttributeRequiredByMetadataValidator;
import org.keycloak.userprofile.validator.BlankAttributeValidator;
import org.keycloak.userprofile.validator.ImmutableAttributeValidator;
import org.keycloak.util.JsonSerialization;
import org.keycloak.validate.AbstractSimpleValidator;
@ -68,7 +66,6 @@ import org.keycloak.validate.ValidatorConfig;
public class DeclarativeUserProfileProvider implements UserProfileProvider {
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_UP_CONFIG_COMPONENT_KEY = "kc.parsed.up.config";
@ -94,7 +91,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
}
private final KeycloakSession session;
private final boolean isDeclarativeConfigurationEnabled;
private final String providerId;
private final Map<UserProfileContext, UserProfileMetadata> contextualMetadataRegistry;
protected final UPConfig parsedDefaultRawConfig;
@ -102,23 +98,17 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
public DeclarativeUserProfileProvider(KeycloakSession session, DeclarativeUserProfileProviderFactory factory) {
this.session = session;
this.providerId = factory.getId();
this.isDeclarativeConfigurationEnabled = factory.isDeclarativeConfigurationEnabled();
this.contextualMetadataRegistry = factory.getContextualMetadataRegistry();
this.parsedDefaultRawConfig = factory.getParsedDefaultRawConfig();
}
protected Attributes createAttributes(UserProfileContext context, Map<String, ?> attributes,
UserModel user, UserProfileMetadata metadata) {
RealmModel realm = session.getContext().getRealm();
if (isEnabled(realm)) {
if (user != null && user.getServiceAccountClientLink() != null) {
return new LegacyAttributes(context, attributes, user, metadata, session);
}
return new DefaultAttributes(context, attributes, user, metadata, session);
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);
}
@Override
@ -187,16 +177,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
UserProfileMetadata decoratedMetadata = metadata.clone();
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);
if (component == null) {
@ -218,12 +198,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
@Override
public UPConfig getConfiguration() {
RealmModel realm = session.getContext().getRealm();
if (!isEnabled(realm)) {
return parsedDefaultRawConfig.clone();
}
Optional<ComponentModel> component = getComponentModel();
if (component.isPresent()) {
@ -522,11 +496,6 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
model.getConfig().remove(UP_COMPONENT_CONFIG_KEY);
}
@Override
public boolean isEnabled(RealmModel realm) {
return isDeclarativeConfigurationEnabled && realm.getAttribute(REALM_USER_PROFILE_ENABLED, false);
}
@Override
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 adminReadOnlyAttributesPattern = getRegexPatternString(DEFAULT_ADMIN_READ_ONLY_ATTRIBUTES);
private boolean isDeclarativeConfigurationEnabled;
private UPConfig parsedDefaultRawConfig;
private final Map<UserProfileContext, UserProfileMetadata> contextualMetadataRegistry = new HashMap<>();
@ -198,7 +196,6 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
@Override
public void init(Config.Scope config) {
isDeclarativeConfigurationEnabled = Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_USER_PROFILE);
parsedDefaultRawConfig = UPConfigUtils.parseDefaultConfig();
// make sure registry is clear in case of re-deploy
@ -313,12 +310,8 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
* @return the metadata
*/
protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata) {
if (isDeclarativeConfigurationEnabled) {
// default metadata for each context is based on the default realm configuration
return new DeclarativeUserProfileProvider(null, this).decorateUserProfileForCache(metadata, parsedDefaultRawConfig);
}
return metadata;
// default metadata for each context is based on the default realm configuration
return new DeclarativeUserProfileProvider(null, this).decorateUserProfileForCache(metadata, parsedDefaultRawConfig);
}
private AttributeValidatorMetadata createReadOnlyAttributeUnchangedValidator(Pattern pattern) {
@ -461,10 +454,6 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
// GETTER METHODS FOR INTERNAL FIELDS
protected boolean isDeclarativeConfigurationEnabled() {
return isDeclarativeConfigurationEnabled;
}
protected UPConfig getParsedDefaultRawConfig() {
return parsedDefaultRawConfig;
}

View file

@ -524,7 +524,7 @@ so please make sure you rebuild all `testsuite/integration-arquillian` child mod
## 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.
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;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import jakarta.ws.rs.BadRequestException;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.representations.account.UserRepresentation;
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.testsuite.admin.ApiUtil;
import org.keycloak.userprofile.UserProfileConstants;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.not;
@ -47,6 +55,46 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
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
public void testUpdateProfileCannotUpdateReadOnlyAttributes() throws IOException {
// Denied by default
@ -85,9 +133,10 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
testAccountUpdateAttributeExpectFailure("saml.persistent.name.id.for._foo_");
testAccountUpdateAttributeExpectSuccess("saml.persistent.name.idafor.foo");
// TODO: Uncomment similarly like above
// Special characters inside should be quoted
testAccountUpdateAttributeExpectFailure("deniedsome/thing");
testAccountUpdateAttributeExpectFailure("deniedsome*thing");
//testAccountUpdateAttributeExpectFailure("deniedsome/thing");
//testAccountUpdateAttributeExpectFailure("deniedsome*thing");
testAccountUpdateAttributeExpectSuccess("deniedsomeithing");
// Denied only for admin, but allowed for normal user
@ -135,9 +184,10 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
user.singleAttribute(attrName, "foo-updated");
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 = updateAndGet(user);
updateError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
user = get();
assertTrue(user.getAttributes().containsKey(attrName));
// Revert with admin REST
@ -178,6 +228,10 @@ public class AccountRestServiceReadOnlyAttributesTest extends AbstractRestServic
private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
assertEquals(204, status);
return get();
}
private UserRepresentation get() throws IOException {
return SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
}

View file

@ -17,8 +17,10 @@
package org.keycloak.testsuite.account;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.http.impl.client.CloseableHttpClient;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
@ -66,11 +68,11 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.TokenUtil;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.validate.validators.EmailValidator;
import jakarta.ws.rs.ClientErrorException;
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.assertNull;
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>
@ -99,7 +102,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Override
@Before
public void before() {
super.before();
setUserProfileConfiguration(null);
}
@Test
public void testEditUsernameAllowed() throws IOException {
UserRepresentation user = getUser();
@ -115,14 +125,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(true);
realm.update(realmRep);
user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata());
// can write both username and email
assertUserProfileAttributeMetadata(user, "username", "${username}", true, false);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
assertUserProfileAttributeMetadata(user, "firstName", "${firstName}", true, false);
assertUserProfileAttributeMetadata(user, "lastName", "${lastName}", true, false);
}
assertNotNull(user.getUserProfileMetadata());
// can write both username and email
assertUserProfileAttributeMetadata(user, "username", "${username}", true, false);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
assertUserProfileAttributeMetadata(user, "firstName", "${firstName}", true, false);
assertUserProfileAttributeMetadata(user, "lastName", "${lastName}", true, false);
user.setUsername("changed-username");
user.setEmail("changed-email@keycloak.org");
user = updateAndGet(user);
@ -133,12 +143,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(false);
realm.update(realmRep);
user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata());
// username is readonly but email is writable
assertUserProfileAttributeMetadata(user, "username", "${username}", true, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
}
assertNotNull(user.getUserProfileMetadata());
// username is readonly but email is writable
assertUserProfileAttributeMetadata(user, "username", "${username}", true, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
user.setUsername("should-not-change");
user.setEmail("changed-email@keycloak.org");
updateError(user, 400, Messages.READ_ONLY_USERNAME);
@ -147,13 +157,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(true);
realm.update(realmRep);
user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata());
// username is read-only, not required, and is the same as email
// but email is writable
assertUserProfileAttributeMetadata(user, "username", "${username}", false, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
}
assertNotNull(user.getUserProfileMetadata());
// username is read-only, not required, and is the same as email
// but email is writable
assertUserProfileAttributeMetadata(user, "username", "${username}", false, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, false);
user.setUsername("should-be-the-email");
user.setEmail("user@keycloak.org");
user = updateAndGet(user);
@ -164,12 +174,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setEditUsernameAllowed(false);
realm.update(realmRep);
user = getUser();
if (isDeclarativeUserProfile()) {
assertNotNull(user.getUserProfileMetadata());
// username is read-only and is the same as email, but email is read-only
assertUserProfileAttributeMetadata(user, "username", "${username}", false, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, true);
}
assertNotNull(user.getUserProfileMetadata());
// username is read-only and is the same as email, but email is read-only
assertUserProfileAttributeMetadata(user, "username", "${username}", false, true);
assertUserProfileAttributeMetadata(user, "email", "${email}", true, true);
user.setUsername("should-be-the-email");
user.setEmail("should-not-change@keycloak.org");
user = updateAndGet(user);
@ -209,46 +219,8 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
UserRepresentation user = getUser(false);
assertNull(user.getUserProfileMetadata());
}
@Test
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) {
protected static UserProfileAttributeMetadata getUserProfileAttributeMetadata(UserRepresentation user, String attName) {
if(user.getUserProfileMetadata() == null)
return null;
for(UserProfileAttributeMetadata uam : user.getUserProfileMetadata().getAttributes()) {
@ -259,14 +231,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
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);
if (isDeclarativeUserProfile()) {
assertNotNull(uam);
assertEquals("Unexpected display name for attribute " + uam.getName(), displayName, uam.getDisplayName());
assertEquals("Unexpected required flag for attribute " + uam.getName(), required, uam.isRequired());
assertEquals("Unexpected readonly flag for attribute " + uam.getName(), readOnly, uam.isReadOnly());
}
assertNotNull(uam);
assertEquals("Unexpected display name for attribute " + uam.getName(), displayName, uam.getDisplayName());
assertEquals("Unexpected required flag for attribute " + uam.getName(), required, uam.isRequired());
assertEquals("Unexpected readonly flag for attribute " + uam.getName(), readOnly, uam.isReadOnly());
return uam;
}
@ -284,6 +256,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test
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();
String originalUsername = user.getUsername();
String originalFirstName = user.getFirstName();
@ -374,6 +353,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@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 + "}"
+ "]}");
UserRepresentation user = getUser();
String originalUsername = user.getUsername();
String originalFirstName = user.getFirstName();
@ -426,6 +412,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test
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();
String originalUsername = user.getUsername();
String originalFirstName = user.getFirstName();
@ -602,6 +596,10 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
protected UserRepresentation getUser(boolean fetchMetadata) throws IOException {
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());
try {
@ -609,7 +607,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
} catch (IOException e) {
System.err.println("Error during user reading: " + a.asString());
throw e;
}
}
}
protected UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
@ -1719,7 +1717,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
}
}
protected boolean isDeclarativeUserProfile() {
return false;
protected void setUserProfileConfiguration(String configuration) {
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.assertNotNull;
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_ADMIN_EDITABLE;
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.Test;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.Profile;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
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.account.UserRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.userprofile.UserProfileContext;
/**
*
* Test account rest service with custom user profile configurations
*
* @author Vlastimil Elias <velias@redhat.com>
*
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTest {
public class AccountRestServiceWithUserProfileTest extends AbstractRestServiceTest {
@Override
@Before
public void before() {
super.before();
enableDynamicUserProfile();
setUserProfileConfiguration(null);
}
@Override
protected boolean isDeclarativeUserProfile() {
return true;
}
private final static String UP_CONFIG_FOR_METADATA = "{\"attributes\": ["
+ "{\"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\"}},"
@ -101,9 +95,7 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
@Test
@Override
public void testEditUsernameAllowed() throws IOException {
super.testEditUsernameAllowed();
setUserProfileConfiguration(UP_CONFIG_FOR_METADATA);
UserRepresentation user = getUser();
@ -221,7 +213,6 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
@Test
@Override
public void testEditUsernameDisallowed() throws IOException {
try {
@ -339,40 +330,6 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
assertEquals(204, response.getStatus());
}
}
@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
public void testManageUserLocaleAttribute() throws IOException {
@ -412,17 +369,29 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
updateAndGet(user);
}
}
protected void setUserProfileConfiguration(String configuration) {
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
}
protected void enableDynamicUserProfile() {
RealmRepresentation testRealm = testRealm().toRepresentation();
VerifyProfileTest.enableDynamicUserProfile(testRealm);
testRealm().update(testRealm);
protected UserRepresentation getUser() throws IOException {
return getUser(true);
}
protected UserRepresentation getUser(boolean fetchMetadata) throws IOException {
String accountUrl = getAccountUrl(null) + "?userProfileMetadata=" + fetchMetadata;
return AccountRestServiceTest.getUser(accountUrl, httpClient, tokenUtil);
}
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;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
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.Users;
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.LoginTotpPage;
import org.keycloak.testsuite.pages.PageUtils;
@ -86,6 +89,12 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
testLoginOneTimeCodePage.setAuthRealm(testRealmPage);
}
@Before
public void configureUserProfile() {
UserProfileResource userProfileRes = testRealmResource().users().userProfile();
VerifyProfileTest.enableUnmanagedAttributes(userProfileRes);
}
private void configureRequiredActions() {
//set configure TOTP as required action to test user
List<String> requiredActions = new ArrayList<>();

View file

@ -16,13 +16,25 @@
*/
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.keycloak.userprofile.UserProfileConstants.ROLE_USER;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
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 {
@ -41,6 +53,46 @@ public class AppInitiatedActionUpdateEmailTest extends AbstractAppInitiatedActio
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
protected void changeEmailUsingAIA(String newEmail) throws Exception {
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;
/**
* 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
*/
public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedActionTest {
@ -51,10 +53,6 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
@Page
protected ErrorPage errorPage;
protected boolean isDynamicForm() {
return false;
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
@ -212,10 +210,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
Assert.assertEquals("New last", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm())
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
else
Assert.assertEquals("Please specify first name.", updateProfilePage.getInputErrors().getFirstNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
events.assertEmpty();
}
@ -237,10 +232,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
Assert.assertEquals("", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm())
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
else
Assert.assertEquals("Please specify last name.", updateProfilePage.getInputErrors().getLastNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
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.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.events.Details;
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.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
@ -66,10 +68,6 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
@Page
protected ErrorPage errorPage;
protected boolean isDynamicForm() {
return false;
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
@ -177,11 +175,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
Assert.assertEquals("", updateProfilePage.getFirstName());
Assert.assertEquals("New last", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm())
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
else
Assert.assertEquals("Please specify first name.", updateProfilePage.getInputErrors().getFirstNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError());
events.assertEmpty();
}
@ -203,10 +197,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
Assert.assertEquals("", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
if(isDynamicForm())
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
else
Assert.assertEquals("Please specify last name.", updateProfilePage.getInputErrors().getLastNameError());
Assert.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError());
events.assertEmpty();
}
@ -350,37 +341,47 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
@Test
public void updateProfileWithoutRemoveCustomAttributes() {
UserRepresentation userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
UserResource user = adminClient.realm("test").users().get(userRep.getId());
UserProfileResource upResource = adminClient.realm("test").users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT);
upResource.update(upConfig);
userRep.setAttributes(new HashMap<>());
userRep.getAttributes().put("custom", Arrays.asList("custom"));
try {
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();
assertFalse(updateProfilePage.isCancelDisplayed());
loginPage.login("test-user@localhost", "password");
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
userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
Assert.assertEquals("New first", userRep.getFirstName());
Assert.assertEquals("New last", userRep.getLastName());
Assert.assertEquals("new@email.com", userRep.getEmail());
Assert.assertEquals("test-user@localhost", userRep.getUsername());
Assert.assertNotNull(userRep.getAttributes());
Assert.assertTrue(userRep.getAttributes().containsKey("custom"));
events.expectLogin().assertEvent();
// assert user is really updated in persistent store
userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
Assert.assertEquals("New first", userRep.getFirstName());
Assert.assertEquals("New last", userRep.getLastName());
Assert.assertEquals("new@email.com", userRep.getEmail());
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 org.apache.commons.lang3.StringUtils;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
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.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
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.VerifyProfileTest;
import org.keycloak.testsuite.pages.AppPage;
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.KeycloakModelUtils;
import org.keycloak.testsuite.util.UserBuilder;
import org.openqa.selenium.By;
/**
* Test update-profile required action with custom user profile configurations
*
* @author Vlastimil Elias <velias@redhat.com>
*
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActionUpdateProfileTest {
public class RequiredActionUpdateProfileWithUserProfileTest extends AbstractTestRealmKeycloakTest {
protected static final String PASSWORD = "password";
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_optional;
@Override
protected boolean isDynamicForm() {
return true;
}
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected AppPage appPage;
@Page
protected LoginPage loginPage;
@Page
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
@Page
protected ErrorPage errorPage;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
testRealm.setClientScopes(new ArrayList<>());
testRealm.getClientScopes().add(ClientScopeBuilder.create().name(SCOPE_DEPARTMENT).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
public void beforeTest() {
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
@ -619,16 +652,6 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
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) {
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.junit.AfterClass;
import org.junit.Before;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractAuthTest;
import org.keycloak.testsuite.adapter.page.AppServerContextRoot;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.ServerURLs;
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
@Override
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.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.PERMISSIONS_ALL;
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
import static org.keycloak.testsuite.forms.VerifyProfileTest.setUserProfileConfiguration;
import org.junit.After;
@ -21,7 +19,6 @@ import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
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.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.UserBuilder;
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>
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class DeclarativeUserTest extends AbstractAdminTest {
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();
realmRep.setInternationalizationEnabled(true);
realmRep.setSupportedLocales(new HashSet<>(Arrays.asList("en", "de")));
enableDynamicUserProfile(realmRep);
realm.update(realmRep);
setUserProfileConfiguration(realm, "{\"attributes\": ["
+ "{\"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.common.Profile;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.CibaConfig;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.OTPCredentialModel;
@ -302,33 +303,25 @@ public class PermissionsTest extends AbstractKeycloakTest {
}
}, Resource.REALM, false, true);
try (RealmAttributeUpdater updater = new RealmAttributeUpdater(adminClient.realm(REALM_NAME))
.setAttribute(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString())
.update()) {
RealmRepresentation realm = clients.get(AdminRoles.QUERY_REALMS).realm(REALM_NAME).toRepresentation();
assertGettersEmpty(realm);
assertNull(realm.isRegistrationEmailAsUsername());
assertNull(realm.getAttributes());
RealmRepresentation realm = clients.get(AdminRoles.QUERY_REALMS).realm(REALM_NAME).toRepresentation();
assertGettersEmpty(realm);
assertNull(realm.isRegistrationEmailAsUsername());
assertNull(realm.getAttributes());
realm = clients.get(AdminRoles.VIEW_USERS).realm(REALM_NAME).toRepresentation();
assertNotNull(realm.isRegistrationEmailAsUsername());
assertNotNull(realm.getAttributes());
assertNotNull(realm.getAttributes().get(DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED));
realm = clients.get(AdminRoles.VIEW_USERS).realm(REALM_NAME).toRepresentation();
assertNotNull(realm.isRegistrationEmailAsUsername());
realm = clients.get(AdminRoles.MANAGE_USERS).realm(REALM_NAME).toRepresentation();
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();
assertNotNull(realm.isRegistrationEmailAsUsername());
// query users only if granted through fine-grained admin
realm = clients.get(AdminRoles.QUERY_USERS).realm(REALM_NAME).toRepresentation();
assertNull(realm.isRegistrationEmailAsUsername());
assertNull(realm.getAttributes());
}
// query users only if granted through fine-grained admin
realm = clients.get(AdminRoles.QUERY_USERS).realm(REALM_NAME).toRepresentation();
assertNull(realm.isRegistrationEmailAsUsername());
assertNull(realm.getAttributes());
// 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) {
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() {

View file

@ -17,11 +17,11 @@
package org.keycloak.testsuite.admin;
import jakarta.ws.rs.WebApplicationException;
import org.hamcrest.Matchers;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Assert;
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.RealmResource;
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.UsersResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Base64;
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.RequiredActionProviderRepresentation;
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.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.UserStorageProvider;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.federation.UserMapStorageFactory;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.page.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.ErrorPage;
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.RoleBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.validator.UsernameProhibitedCharactersValidator;
import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
@ -180,8 +186,18 @@ public class UserTest extends AbstractAdminTest {
}
@Before
public void beforeUserTest() {
public void beforeUserTest() throws IOException {
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();
}
@ -1241,16 +1257,27 @@ public class UserTest extends AbstractAdminTest {
@Test
public void wildcardSearch() {
Assume.assumeFalse("Default validators do not allow special chars", isDeclarativeUserProfile());
createUser("0user\\\\0", "email0@emal");
createUser("1user\\\\", "email1@emal");
createUser("2user\\\\%", "email2@emal");
createUser("3user\\\\*", "email3@emal");
createUser("4user\\\\_", "email4@emal");
UserProfileResource upResource = realm.users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
Map<String, Object> prohibitedCharsOrigCfg = upConfig.getAttribute(UserModel.USERNAME).getValidations().get(UsernameProhibitedCharactersValidator.ID);
upConfig.getAttribute(UserModel.USERNAME).getValidations().remove(UsernameProhibitedCharactersValidator.ID);
upResource.update(upConfig);
assertAdminEvents.clear();
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));
try {
createUser("0user\\\\0", "email0@emal");
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
@ -2465,20 +2492,16 @@ public class UserTest extends AbstractAdminTest {
UserRepresentation update = new UserRepresentation();
update.setId(userId);
if (isDeclarativeUserProfile()) {
// user profile requires sending all attributes otherwise they are removed
update.setEmail(email);
}
// user profile requires sending all attributes otherwise they are removed
update.setEmail(email);
update.setAttributes(Map.of("phoneNumber", List.of("123")));
updateUser(realm.users().get(userId), update);
UserRepresentation updated = realm.users().get(userId).toRepresentation();
assertThat(updated.getUsername(), equalTo(userName));
if (isDeclarativeUserProfile()) {
assertThat(updated.getAttributes().get("phoneNumber"), equalTo(List.of("123")));
} else {
assertThat(updated.getAttributes(), equalTo(Map.of("phoneNumber", List.of("123"))));
}
assertThat(updated.getAttributes().get("phoneNumber"), equalTo(List.of("123")));
assertThat(updated.getEmail(), equalTo(email));
}
@ -2494,9 +2517,7 @@ public class UserTest extends AbstractAdminTest {
try {
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) {
ErrorRepresentation error = expected.getResponse().readEntity(ErrorRepresentation.class);
assertEquals("error-user-attribute-read-only", error.getErrorMessage());
@ -2869,26 +2890,42 @@ public class UserTest extends AbstractAdminTest {
@Test
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++) {
users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close();
try {
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
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();
for (int i = 0; i < 110; i++) {
@ -2900,6 +2937,10 @@ public class UserTest extends AbstractAdminTest {
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.nullValue());
}
} finally {
upConfig.removeAttribute("aName");
upResource.update(upConfig);
}
}
@Test
@ -3461,7 +3502,75 @@ public class UserTest extends AbstractAdminTest {
);
}
protected boolean isDeclarativeUserProfile() {
return false;
@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());
}
@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.RealmResource;
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.UsersResource;
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.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.updaters.Creator;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ClientBuilder;
@ -1281,6 +1284,10 @@ public class GroupTest extends AbstractGroupTest {
String groupName = "brief-grouptest-group";
String userName = "brief-grouptest-user";
// enable user profile unmanaged attributes
UserProfileResource upResource = realm.users().userProfile();
UPConfig cfg = VerifyProfileTest.enableUnmanagedAttributes(upResource);
GroupsResource groups = realm.groups();
try (Response response = groups.add(GroupBuilder.create().name(groupName).build())) {
String groupId = ApiUtil.getCreatedId(response);
@ -1308,6 +1315,9 @@ public class GroupTest extends AbstractGroupTest {
group.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.assertNotNull;
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 java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.common.Profile;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeGroupMetadata;
import org.keycloak.representations.idm.UserProfileMetadata;
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.UPConfig;
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>
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class UserProfileAdminTest extends AbstractAdminTest {
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
}
@Test

View file

@ -31,6 +31,7 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
import org.keycloak.testsuite.pages.IdpLinkEmailPage;
@ -179,8 +180,13 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
@Before
public void beforeBrokerTest() {
importRealm(bc.createConsumerRealm());
importRealm(bc.createProviderRealm());
RealmRepresentation consumerRealm = bc.createConsumerRealm();
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

View file

@ -64,17 +64,6 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
@Rule
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);
}
/**

View file

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

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
import org.apache.commons.lang3.StringUtils;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test;
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.UserRepresentation;
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.RegisterPage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.openqa.selenium.By;
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.assertTrue;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
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>
@ -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"));
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
protected String getMapperTemplate() {
return "kc-oidc-idp-[%s]";
return "kc-oidc-idp-%s";
}
@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.setIdentityProviderMapper(UsernameTemplateMapper.PROVIDER_ID);
userTemplateImporterMapper.setConfig(ImmutableMap.<String, String>builder()
.put(UsernameTemplateMapper.TEMPLATE, "${ALIAS}:${CLAIM.sub}")
.put(UsernameTemplateMapper.TEMPLATE, "${ALIAS}_${CLAIM.sub}")
.build());
IdentityProviderMapperRepresentation jwtClaimsAttrMapper = new IdentityProviderMapperRepresentation();
@ -71,22 +71,6 @@ public class UsernameTemplateMapperTest extends AbstractBaseBrokerTest {
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
*/
@ -99,5 +83,8 @@ public class UsernameTemplateMapperTest extends AbstractBaseBrokerTest {
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));
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();
testRealm.setRealm("test_" + RandomStringUtils.randomAlphabetic(5));
testRealm.setEnabled(true);
testRealm.setEditUsernameAllowed(true);
return testRealm;
}

View file

@ -265,13 +265,7 @@ public class ExportImportTest extends AbstractKeycloakTest {
@Test
public void testExportUserProfileConfig() throws IOException {
//Enable user profile on 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
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>
*/
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-standalone-connection.properties";
@ -188,39 +187,31 @@ public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
@Test
public void testUserProfile() throws Exception {
RealmRepresentation realm = testRealmResource().toRepresentation();
VerifyProfileTest.enableDynamicUserProfile(realm);
testRealmResource().update(realm);
assertSuccessfulSpnegoLogin("hnelson", "hnelson", "secret");
try {
assertSuccessfulSpnegoLogin("hnelson", "hnelson", "secret");
// User-profile data should be present (including KERBEROS_PRINCIPAL attribute)
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)
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);
// KERBEROS_PRINCIPAL attribute should be read-only and should be in "User metadata" group
UserProfileAttributeMetadata krbPrincipalAttribute = john.getUserProfileMetadata().getAttributeMetadata(KerberosConstants.KERBEROS_PRINCIPAL);
Assert.assertTrue(krbPrincipalAttribute.isReadOnly());
Assert.assertEquals(USER_METADATA_GROUP, krbPrincipalAttribute.getGroup());
// KERBEROS_PRINCIPAL attribute should be read-only and should be in "User metadata" group
UserProfileAttributeMetadata krbPrincipalAttribute = john.getUserProfileMetadata().getAttributeMetadata(KerberosConstants.KERBEROS_PRINCIPAL);
Assert.assertTrue(krbPrincipalAttribute.isReadOnly());
Assert.assertEquals(USER_METADATA_GROUP, krbPrincipalAttribute.getGroup());
// Test Update profile
john.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PROFILE.toString());
johnResource.update(john);
// Test Update profile
john.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PROFILE.toString());
johnResource.update(john);
Response spnegoResponse = spnegoLogin("hnelson", "secret");
Assert.assertEquals(200, spnegoResponse.getStatus());
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");
Assert.assertEquals(200, spnegoResponse.getStatus());
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());
}
john.getRequiredActions().remove(UserModel.RequiredAction.UPDATE_PROFILE.toString());
johnResource.update(john);
}
}

View file

@ -19,7 +19,6 @@
package org.keycloak.testsuite.federation.ldap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
@ -33,6 +32,7 @@ import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.federation.kerberos.KerberosFederationProvider;
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.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.account.AccountCredentialResource;
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.LDAPTestUtils;
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.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
/**
@ -115,56 +114,48 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
public void testUpdateProfile() throws IOException {
UserRepresentation user = getProfile();
List<String> origLdapId = new ArrayList<>(user.getAttributes().get(LDAPConstants.LDAP_ID));
List<String> origLdapEntryDn = new ArrayList<>(user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN));
Assert.assertEquals(1, origLdapId.size());
Assert.assertEquals(1, origLdapEntryDn.size());
assertThat(user.getAttributes().keySet(), not(contains(KerberosFederationProvider.KERBEROS_PRINCIPAL)));
// Metadata attributes like LDAP_ID are not present
Assert.assertNull(user.getAttributes());
// 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.setLastName("DoeUpdated");
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
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.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);
// Trying to update LDAP_ID should fail (Updating existing attribute, which is present on the user even if not visible to the user)
user.singleAttribute(LDAPConstants.LDAP_ID, "123");
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();
assertFalse(user.getAttributes().get(LDAPConstants.LDAP_ID).isEmpty());
Assert.assertNull(user.getAttributes());
// Trying to update LDAP_ENTRY_DN should fail
user.getAttributes().put(LDAPConstants.LDAP_ID, origLdapId);
user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN).remove(0);
user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN).add("ou=foo,dc=bar");
user.singleAttribute(LDAPConstants.LDAP_ENTRY_DN, "ou=foo,dc=bar");
updateProfileExpectError(user, 400, Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED);
// Update firstName and lastName should be fine
user.getAttributes().put(LDAPConstants.LDAP_ENTRY_DN, origLdapEntryDn);
user.getAttributes().remove(LDAPConstants.LDAP_ENTRY_DN);
updateProfileExpectSuccess(user);
user = getProfile();
assertEquals("JohnUpdated", user.getFirstName());
assertEquals("DoeUpdated", user.getLastName());
assertEquals(origLdapId, user.getAttributes().get(LDAPConstants.LDAP_ID));
assertEquals(origLdapEntryDn, user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN));
Assert.assertNull(user.getAttributes());
// Revert
user.setFirstName("John");
@ -172,6 +163,67 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
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
public void testGetCredentials() throws IOException {
List<AccountCredentialResource.CredentialContainer> credentials = getCredentials();
@ -231,7 +283,8 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
assertEquals("john-alias@email.org", usernew.getEmail());
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
usernew.setEmail("john@email.org");
@ -240,13 +293,22 @@ public class LDAPAccountRestApiTest extends AbstractLDAPTest {
org.keycloak.representations.idm.UserRepresentation userRep = testRealm().users()
.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);
testRealm().users().get(userRep.getId()).update(userRep);
usernew = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
assertTrue(usernew.getAttributes().containsKey(LDAPConstants.LDAP_ID));
assertTrue(usernew.getAttributes().containsKey(LDAPConstants.LDAP_ENTRY_DN));
// Metadata attributes still not present in account REST
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jakarta.ws.rs.BadRequestException;
@ -36,7 +38,11 @@ import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
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.storage.UserStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
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.junit.Assert.assertEquals;
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");
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
});
UPConfig cfg = testRealm().users().userProfile().getConfiguration();
cfg.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
testRealm().users().userProfile().update(cfg);
}
@Test
@ -245,4 +259,75 @@ public class LDAPAdminRestApiTest extends AbstractLDAPTest {
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.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
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.UserAttributeLDAPStorageMapper;
import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.testsuite.util.LDAPTestUtils;
@ -85,6 +87,10 @@ public class LDAPBinaryAttributesTest extends AbstractLDAPTest {
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.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserRepresentation;
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.ldap.idm.model.LDAPObject;
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.util.LDAPRule;
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>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class LDAPUserProfileTest extends AbstractLDAPTest {
@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");
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john2, "Password1");
});
RealmRepresentation realm = testRealm().toRepresentation();
VerifyProfileTest.enableDynamicUserProfile(realm);
testRealm().update(realm);
}
@Test

View file

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

View file

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

View file

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

View file

@ -56,6 +56,8 @@ import org.keycloak.testsuite.util.AccountHelper;
import jakarta.mail.internet.MimeMessage;
import jakarta.ws.rs.core.Response;
import org.keycloak.testsuite.util.WaitUtils;
import java.io.IOException;
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();
}

View file

@ -34,26 +34,34 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
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.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
/**
* Test user registration with customized user-profile configurations
*
* @author Vlastimil Elias <velias@redhat.com>
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class RegisterWithUserProfileTest extends RegisterTest {
public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
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\": {}},"
+ "{\"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
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
VerifyProfileTest.enableDynamicUserProfile(testRealm);
testRealm.setClientScopes(new ArrayList<>());
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());
@ -683,7 +704,11 @@ public class RegisterWithUserProfileTest extends RegisterTest {
}
}
protected void setUserProfileConfiguration(String configuration) {
private void setUserProfileConfiguration(String 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.assertFalse;
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_USER;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@ -41,6 +39,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.common.Profile;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
@ -79,7 +78,6 @@ import org.openqa.selenium.By;
/**
* @author Vlastimil Elias <velias@redhat.com>
*/
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE)
public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
public static final String SCOPE_DEPARTMENT = "department";
@ -117,9 +115,6 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
@Override
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 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();
@ -404,15 +399,8 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
@Test
public void testIgnoreCustomAttributeWhenUserProfileIsDisabled() {
try {
disableDynamicUserProfile(testRealm());
testingClient.server(TEST_REALM_NAME).run(setEmptyFirstNameAndCustomAttribute());
testDefaultProfile();
} finally {
RealmRepresentation realm = testRealm().toRepresentation();
enableDynamicUserProfile(realm);
testRealm().update(realm);
}
testingClient.server(TEST_REALM_NAME).run(setEmptyFirstNameAndCustomAttribute());
testDefaultProfile();
}
private static RunOnServer setEmptyFirstNameAndCustomAttribute() {
@ -1166,7 +1154,7 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
}
@Test
public void testConfigurationRemainsAfterReset() throws IOException {
public void testConfigurationPersisted() throws IOException {
String customConfig = "{\"attributes\": ["
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
@ -1175,13 +1163,7 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
UPConfig persistedConfig = setUserProfileConfiguration(customConfig);
RealmResource realmRes = testRealm();
disableDynamicUserProfile(realmRes, false);
RealmRepresentation realm = realmRes.toRepresentation();
enableDynamicUserProfile(realm);
testRealm().update(realm);
JsonTestUtils.assertJsonEquals(JsonSerialization.writeValueAsString(persistedConfig), realmRes.users().userProfile().getConfiguration());
JsonTestUtils.assertJsonEquals(JsonSerialization.writeValueAsString(persistedConfig), testRealm().users().userProfile().getConfiguration());
}
protected UserRepresentation getUser(String userId) {
@ -1211,30 +1193,6 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
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) {
try {
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) {
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.assertTrue;
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_LINKS;
import static org.keycloak.models.AccountRoles.VIEW_GROUPS;
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
import static org.keycloak.testsuite.Assert.assertNames;
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;
/**
@ -1204,7 +1204,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
RealmRepresentation rep = realm.toRepresentation();
Map<String, String> attributes = rep.getAttributes();
String userProfileEnabled = attributes.get(REALM_USER_PROFILE_ENABLED);
assertTrue(Boolean.parseBoolean(userProfileEnabled));
assertNull(userProfileEnabled);
}
private void testUnmanagedAttributePolicySet(RealmResource realm, UnmanagedAttributePolicy policy) {

View file

@ -17,10 +17,8 @@
package org.keycloak.testsuite.migration;
import org.junit.Test;
import org.keycloak.common.Profile.Feature;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.utils.io.IOUtil;
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.
*/
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class JsonFileImport1903MigrationTest extends AbstractJsonFileImportMigrationTest {
@Override

View file

@ -19,9 +19,7 @@ package org.keycloak.testsuite.migration;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.migration.Migration;
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>
*/
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
public class MigrationTest extends AbstractMigrationTest {
@Override

View file

@ -139,7 +139,6 @@ public class ImportTest extends AbstractTestRealmKeycloakTest {
}
@Test
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public void importUserProfile() throws Exception {
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.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
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.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.ProtocolMappersUpdater;
import org.keycloak.testsuite.util.AdminClientUtil;
@ -120,6 +123,10 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
* @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
*/
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.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.models.utils.KeycloakModelUtils;
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.testsuite.AbstractKeycloakTest;
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.RealmBuilder;
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>
*/
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
public class ServiceAccountUserProfileTest extends AbstractKeycloakTest {
private static String userId;
@ -116,7 +112,6 @@ public class ServiceAccountUserProfileTest extends AbstractKeycloakTest {
realm.user(serviceAccountUser);
RealmRepresentation realmRep = realm.build();
VerifyProfileTest.enableDynamicUserProfile(realmRep);
testRealms.add(realmRep);
}

View file

@ -128,7 +128,7 @@ public class LogoutTest extends AbstractSamlTest {
.targetAttributeSamlResponse()
.targetUri(getSamlBrokerUrl(REALM_NAME))
.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()
// 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.assertFalse;
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.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test;
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.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
@ -52,12 +60,19 @@ public class CustomRegistrationTemplateTest extends AbstractTestRealmKeycloakTes
@Page
protected AppPage appPage;
private UPConfig upConfig;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.setRegistrationAllowed(true);
testRealm.setLoginTheme("address");
}
@Before
public void onBefore() {
upConfig = updateUserProfileConfiguration();
}
@Test
public void testRegistration() {
//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);
}
@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) {
for (Entry<String, String> attribute : CUSTOM_ATTRIBUTES.entrySet()) {
String name = attribute.getKey();
@ -93,9 +152,6 @@ public class CustomRegistrationTemplateTest extends AbstractTestRealmKeycloakTes
}
private void navigateToRegistrationPage() {
RealmRepresentation realm = testRealm().toRepresentation();
realm.setRegistrationAllowed(true);
testRealm().update(realm);
loginPage.open();
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.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.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import jakarta.ws.rs.core.Response;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test;
@ -39,7 +42,11 @@ import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
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.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
@ -63,26 +70,31 @@ public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTe
@Page
protected AppPage appPage;
private UPConfig upConfig;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.setLoginTheme("address");
// the custom theme expects email as username and the username field is not rendered at all
testRealm.setRegistrationEmailAsUsername(true);
}
@Before
public void onBefore() {
UserRepresentation user = UserBuilder.create().enabled(true)
.username("tom")
.email("tom@keycloak.org")
.password("password")
.firstName("Tom")
.lastName("Brady").build();
testRealm.getUsers().add(user);
}
.lastName("Brady")
.requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.name())
.build();
Response resp = testRealm().users().create(user);
String userId = ApiUtil.getCreatedId(resp);
resp.close();
getCleanup().addUserId(userId);
@Before
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);
upConfig = updateUserProfileConfiguration();
}
@Test
@ -94,6 +106,37 @@ public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTe
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() {
navigateToUpdateProfilePage();
updateProfilePage.update(CUSTOM_ATTRIBUTES.entrySet().stream()
@ -124,4 +167,16 @@ public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTe
loginPage.login("tom@keycloak.org", "password");
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