Possibility to email being not required

closes #26552

Signed-off-by: mposolda <mposolda@gmail.com>

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
mposolda 2024-01-29 10:46:57 +01:00 committed by Marek Posolda
parent b41e2f82c4
commit 10ba70c972
5 changed files with 135 additions and 14 deletions

View file

@ -243,6 +243,10 @@ export const AttributeGeneralSettings = () => {
/> />
</FormGroup> </FormGroup>
)} )}
</>
)}
{attributeName !== "username" && (
<>
<Divider /> <Divider />
<FormGroup <FormGroup
label={t("required")} label={t("required")}

View file

@ -358,21 +358,22 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
} }
if (UserModel.EMAIL.equals(attributeName)) { if (UserModel.EMAIL.equals(attributeName)) {
if (context.isAdminContext()) { Predicate<AttributeContext> requiredFromConfig = required;
required = new Predicate<AttributeContext>() { required = new Predicate<AttributeContext>() {
@Override @Override
public boolean test(AttributeContext context) { public boolean test(AttributeContext context) {
UserModel user = context.getUser(); UserModel user = context.getUser();
if (user != null && user.getServiceAccountClientLink() != null) { if (user != null && user.getServiceAccountClientLink() != null) {
return false; return false;
}
RealmModel realm = context.getSession().getContext().getRealm();
return realm.isRegistrationEmailAsUsername();
} }
};
} if (requiredFromConfig.test(context)) return true;
RealmModel realm = context.getSession().getContext().getRealm();
return realm.isRegistrationEmailAsUsername();
}
};
} }
List<AttributeMetadata> existingMetadata = decoratedMetadata.getAttribute(attributeName); List<AttributeMetadata> existingMetadata = decoratedMetadata.getAttribute(attributeName);

View file

@ -53,7 +53,8 @@ public class ModelTestExecutor extends LocalTestExecuter {
// Model test - wrap the call inside the // Model test - wrap the call inside the
TestContext ctx = testContext.get(); TestContext ctx = testContext.get();
KeycloakTestingClient testingClient = ctx.getTestingClient(); KeycloakTestingClient testingClient = ctx.getTestingClient();
testingClient.server().runModelTest(testMethod.getDeclaringClass().getName(), testMethod.getName()); String realmName = annotation.realmName();
testingClient.server(realmName).runModelTest(testMethod.getDeclaringClass().getName(), testMethod.getName());
result.setStatus(TestResult.Status.PASSED); result.setStatus(TestResult.Status.PASSED);
} catch (Throwable e) { } catch (Throwable e) {

View file

@ -33,4 +33,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME) @Retention(RUNTIME)
@Target({ElementType.METHOD}) // TODO: Maybe ElementClass.TYPE too? That way it will be possible to add the annotation on the the test class and not need to add on all the test methods inside the class @Target({ElementType.METHOD}) // TODO: Maybe ElementClass.TYPE too? That way it will be possible to add the annotation on the the test class and not need to add on all the test methods inside the class
public @interface ModelTest { public @interface ModelTest {
/**
* Name of the realm to be set on the KeycloakContext when the test is executed. Defaults to "master" for backwards compatibility
*/
String realmName() default "master";
} }

View file

@ -1257,6 +1257,116 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
@Test
@ModelTest(realmName=TEST_REALM_NAME)
public void testEmailRequired(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, "james");
attributes.put(UserModel.FIRST_NAME, "James");
attributes.put(UserModel.LAST_NAME, "Doe");
UserProfile profile;
// Email required for users by default, but not for admins
UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
provider.setConfiguration(config);
UPAttribute emailOrigConfig = config.getAttribute(UserModel.EMAIL);
Assert.assertEquals(emailOrigConfig.getRequired().getRoles(), Set.of(ROLE_USER)); // Should be required only for users by default
try {
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for users");
} catch (ValidationException ve) {
// expected
}
try {
profile = provider.create(UserProfileContext.USER_API, attributes);
profile.validate();
} catch (ValidationException ve) {
Assert.fail("Should not be here as email is NOT required for administrators");
}
// Test email required in config, registrationEmailAsUsername = false : Email should be required
config.addOrReplaceAttribute(new UPAttribute(UserModel.EMAIL, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_ADMIN, ROLE_USER), Set.of())));
provider.setConfiguration(config);
try {
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for users");
} catch (ValidationException ve) {
// expected
}
try {
profile = provider.create(UserProfileContext.USER_API, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for administrators");
} catch (ValidationException ve) {
// expected
}
// Test email required in config, registrationEmailAsUsername = true : Email should be required
try {
realm.setRegistrationEmailAsUsername(true);
try {
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for users");
} catch (ValidationException ve) {
// expected
}
try {
profile = provider.create(UserProfileContext.USER_API, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for administrators");
} catch (ValidationException ve) {
// expected
}
} finally {
realm.setRegistrationEmailAsUsername(false);
}
// Test email NOT required in config, registrationEmailAsUsername = true : Email should be required
config.addOrReplaceAttribute(new UPAttribute(UserModel.EMAIL, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER)), null));
provider.setConfiguration(config);
try {
realm.setRegistrationEmailAsUsername(true);
try {
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for users");
} catch (ValidationException ve) {
// expected
}
try {
profile = provider.create(UserProfileContext.USER_API, attributes);
profile.validate();
Assert.fail("Should not be here as email is required for administrators");
} catch (ValidationException ve) {
// expected
}
} finally {
realm.setRegistrationEmailAsUsername(false);
}
// Test email NOT required in config, registrationEmailAsUsername = false : Email should NOT be required
try {
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
} catch (ValidationException ve) {
Assert.fail("Should not be here as email is required for users");
}
try {
profile = provider.create(UserProfileContext.USER_API, attributes);
profile.validate();
} catch (ValidationException ve) {
Assert.fail("Should not be here as email is required for administrators");
}
}
@Test @Test
public void testNoValidationsIfUserReadOnly() { public void testNoValidationsIfUserReadOnly() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testNoValidationsIfUserReadOnly); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testNoValidationsIfUserReadOnly);