Skip to content

Commit

Permalink
Enable verify profile required action by default for new realms
Browse files Browse the repository at this point in the history
Closes keycloak#25985

Signed-off-by: rmartinc <[email protected]>
  • Loading branch information
rmartinc authored and mposolda committed Jan 24, 2024
1 parent e736390 commit 7f195ac
Show file tree
Hide file tree
Showing 22 changed files with 231 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.utils.EmailValidationUtil;
import org.keycloak.utils.StringUtil;

import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
Expand All @@ -59,8 +61,13 @@
@ApplicationScoped
public class KeycloakMain implements QuarkusApplication {

private static final Logger log = Logger.getLogger(KeycloakMain.class);
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
private static final String KEYCLOAK_ADMIN_FIRSTNAME_ENV_VAR = "KEYCLOAK_ADMIN_FIRSTNAME";
private static final String KEYCLOAK_ADMIN_LASTNAME_ENV_VAR = "KEYCLOAK_ADMIN_LASTNAME";
private static final String KEYCLOAK_ADMIN_EMAIL_ENV_VAR = "KEYCLOAK_ADMIN_EMAIL";
private static final String KEYCLOAK_ADMIN_DEFAULT_EMAIL_DOMAIN = "keycloak.test";

public static void main(String[] args) {
System.setProperty("kc.version", Version.VERSION);
Expand Down Expand Up @@ -164,17 +171,43 @@ public int run(String... args) throws Exception {
private void createAdminUser() {
String adminUserName = System.getenv(KEYCLOAK_ADMIN_ENV_VAR);
String adminPassword = System.getenv(KEYCLOAK_ADMIN_PASSWORD_ENV_VAR);
String tmpFirstName = System.getenv(KEYCLOAK_ADMIN_FIRSTNAME_ENV_VAR);
String tmpLastName = System.getenv(KEYCLOAK_ADMIN_LASTNAME_ENV_VAR);
String tmpEmail = System.getenv(KEYCLOAK_ADMIN_EMAIL_ENV_VAR);

if ((adminUserName == null || adminUserName.trim().length() == 0)
|| (adminPassword == null || adminPassword.trim().length() == 0)) {
if (StringUtil.isBlank(adminUserName) || StringUtil.isBlank(adminPassword)) {
return;
}

// try to create admin user only with username and password
if (StringUtil.isBlank(tmpFirstName)) {
tmpFirstName = adminUserName;
}

if (StringUtil.isBlank(tmpLastName)) {
tmpLastName = adminUserName;
}

if (StringUtil.isBlank(tmpEmail)) {
tmpEmail = adminUserName + "@" + KEYCLOAK_ADMIN_DEFAULT_EMAIL_DOMAIN;
}

if (!EmailValidationUtil.isValidEmail(tmpEmail)) {
log.errorf("The admin user %s is not created because the associated email is invalid: %s. "
+ "Please set a valid email in the KEYCLOAK_ADMIN_EMAIL environment variable.", adminUserName, tmpEmail);
return;
}

final String adminFirstName = tmpFirstName;
final String adminLastName = tmpLastName;
final String adminEmail = tmpEmail;

KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();

try {
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> {
new ApplianceBootstrap(session).createMasterRealmUser(adminUserName, adminPassword);
new ApplianceBootstrap(session).createMasterRealmUser(adminUserName,
adminPassword, adminFirstName, adminLastName, adminEmail);
});
} catch (Throwable t) {
ServicesLogger.LOGGER.addUserFailed(t, adminUserName, Config.getAdminRealm());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public enum Action {
UPDATE_EMAIL(UserModel.RequiredAction.UPDATE_EMAIL.name(), DefaultRequiredActions::addUpdateEmailAction, () -> isFeatureEnabled(Profile.Feature.UPDATE_EMAIL)),
CONFIGURE_RECOVERY_AUTHN_CODES(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name(), DefaultRequiredActions::addRecoveryAuthnCodesAction, () -> isFeatureEnabled(Profile.Feature.RECOVERY_CODES)),
WEBAUTHN_REGISTER("webauthn-register", DefaultRequiredActions::addWebAuthnRegisterAction, () -> isFeatureEnabled(Profile.Feature.WEB_AUTHN)),
WEBAUTHN_PASSWORDLESS_REGISTER("webauthn-register-passwordless", DefaultRequiredActions::addWebAuthnPasswordlessRegisterAction, () -> isFeatureEnabled(Profile.Feature.WEB_AUTHN));
WEBAUTHN_PASSWORDLESS_REGISTER("webauthn-register-passwordless", DefaultRequiredActions::addWebAuthnPasswordlessRegisterAction, () -> isFeatureEnabled(Profile.Feature.WEB_AUTHN)),
VERIFY_USER_PROFILE(UserModel.RequiredAction.VERIFY_PROFILE.name(), DefaultRequiredActions::addVerifyProfile);

private final String alias;
private final Consumer<RealmModel> addAction;
Expand Down Expand Up @@ -182,6 +183,19 @@ public static void addTermsAndConditionsAction(RealmModel realm) {
}
}

public static void addVerifyProfile(RealmModel realm) {
if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.VERIFY_PROFILE.name()) == null) {
RequiredActionProviderModel termsAndConditions = new RequiredActionProviderModel();
termsAndConditions.setEnabled(true);
termsAndConditions.setAlias(UserModel.RequiredAction.VERIFY_PROFILE.name());
termsAndConditions.setName("Verify Profile");
termsAndConditions.setProviderId(UserModel.RequiredAction.VERIFY_PROFILE.name());
termsAndConditions.setDefaultAction(false);
termsAndConditions.setPriority(90);
realm.addRequiredActionProvider(termsAndConditions);
}
}

public static void addDeleteAccountAction(RealmModel realm) {
if (realm.getRequiredActionProviderByAlias("delete_account") == null) {
RequiredActionProviderModel deleteAccount = new RequiredActionProviderModel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public boolean createMasterRealm() {
return true;
}

public void createMasterRealmUser(String username, String password) {
public void createMasterRealmUser(String username, String password, String firstName, String lastName, String email) {
RealmModel realm = session.realms().getRealmByName(Config.getAdminRealm());
session.getContext().setRealm(realm);

Expand All @@ -103,6 +103,9 @@ public void createMasterRealmUser(String username, String password) {

UserModel adminUser = session.users().addUser(realm, username);
adminUser.setEnabled(true);
adminUser.setFirstName(firstName);
adminUser.setLastName(lastName);
adminUser.setEmail(email);

UserCredentialModel usrCredModel = UserCredentialModel.password(password);
adminUser.credentialManager().updateCredential(usrCredModel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,7 @@ public void importAddUser() {
if (users.getUserByUsername(realm, userRep.getUsername()) != null) {
ServicesLogger.LOGGER.notCreatingExistingUser(userRep.getUsername());
} else {
UserModel user = users.addUser(realm, userRep.getUsername());
user.setEnabled(userRep.isEnabled());
RepresentationToModel.createCredentials(userRep, session, realm, user, false);
RepresentationToModel.createRoleMappings(userRep, user, realm);
UserModel user = RepresentationToModel.createUser(session, realm, userRep);
ServicesLogger.LOGGER.addUserSuccess(userRep.getUsername(), realmRep.getRealm());
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.keycloak.theme.Theme;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.EmailValidationUtil;
import org.keycloak.utils.MediaType;

import java.io.IOException;
Expand Down Expand Up @@ -113,6 +114,9 @@ public Response createUser() {
String username = formData.getFirst("username");
String password = formData.getFirst("password");
String passwordConfirmation = formData.getFirst("passwordConfirmation");
String firstName = formData.getFirst("firstName");
String lastName = formData.getFirst("lastName");
String email = formData.getFirst("email");

if (username != null) {
username = username.trim();
Expand All @@ -130,10 +134,22 @@ public Response createUser() {
return createWelcomePage(null, "Password and confirmation doesn't match");
}

if (firstName == null || firstName.length() == 0) {
return createWelcomePage(null, "FirstName is missing");
}

if (lastName == null || lastName.length() == 0) {
return createWelcomePage(null, "LastName is missing");
}

if (!EmailValidationUtil.isValidEmail(email)) {
return createWelcomePage(null, "Email is invalid");
}

expireCsrfCookie();

ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
applianceBootstrap.createMasterRealmUser(username, password);
applianceBootstrap.createMasterRealmUser(username, password, firstName, lastName, email);

shouldBootstrap.set(false);
ServicesLogger.LOGGER.createdInitialAdminUser(username);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ protected void setupDevConfig() {
try (KeycloakSession session = sessionFactory.create()) {
session.getTransactionManager().begin();
if (new ApplianceBootstrap(session).isNoMasterUser()) {
new ApplianceBootstrap(session).createMasterRealmUser("admin", "admin");
new ApplianceBootstrap(session).createMasterRealmUser("admin", "admin", "admin", "admin", "[email protected]");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ private Process startContainer() throws IOException {

if (!StoreProvider.JPA.equals(StoreProvider.getCurrentProvider())) {
builder.environment().put("KEYCLOAK_ADMIN", "admin");
builder.environment().put("KEYCLOAK_ADMIN_FIRSTNAME", "admin");
builder.environment().put("KEYCLOAK_ADMIN_LASTNAME", "admin");
builder.environment().put("KEYCLOAK_ADMIN_EMAIL", "[email protected]");
builder.environment().put("KEYCLOAK_ADMIN_PASSWORD", "admin");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time;
import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.idm.ClientRepresentation;
Expand Down Expand Up @@ -79,7 +78,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
Expand All @@ -93,6 +91,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import org.keycloak.models.UserModel;
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
Expand Down Expand Up @@ -473,6 +472,10 @@ protected void removeAllRealmsDespiteMaster() {
assertThat(adminClient.realms().findAll().size(), is(equalTo(1)));
}

protected boolean removeVerifyProfileAtImport() {
// remove verify profile by default because most tests are not prepared
return true;
}

public void importRealm(RealmRepresentation realm) {
if (modifyRealmForSSL()) {
Expand Down Expand Up @@ -511,6 +514,19 @@ public void importRealm(RealmRepresentation realm) {
// expected when realm does not exist
}
adminClient.realms().create(realm);

if (removeVerifyProfileAtImport()) {
try {
RequiredActionProviderRepresentation vpModel = adminClient.realm(realm.getRealm()).flows()
.getRequiredAction(UserModel.RequiredAction.VERIFY_PROFILE.name());
vpModel.setEnabled(false);
vpModel.setDefaultAction(false);
adminClient.realm(realm.getRealm()).flows().updateRequiredAction(
UserModel.RequiredAction.VERIFY_PROFILE.name(), vpModel);
testingClient.testing().pollAdminEvent(); // remove the event
} catch (NotFoundException ignore) {
}
}
}

public void removeRealm(String realmName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,25 @@ public static void setupUsers(KeycloakSession session) {
realmUser.credentialManager().updateCredential(UserCredentialModel.password("password"));

UserModel masterUser = session.users().addUser(master, "userAdmin");
masterUser.setFirstName("userAdmin");
masterUser.setLastName("userAdmin");
masterUser.setEmail("[email protected]");
masterUser.grantRole(masterManageUsers);
masterUser.setEnabled(true);
masterUser.credentialManager().updateCredential(UserCredentialModel.password("password"));

UserModel masterAdmin = session.users().addUser(master, "masterAdmin");
masterAdmin.setFirstName("masterAdmin");
masterAdmin.setLastName("masterAdmin");
masterAdmin.setEmail("[email protected]");
masterAdmin.grantRole(masterMasterManageUSers);
masterAdmin.setEnabled(true);
masterAdmin.credentialManager().updateCredential(UserCredentialModel.password("password"));

UserModel user = session.users().addUser(master, "user");
user.setFirstName("user");
user.setLastName("user");
user.setEmail("[email protected]");
user.grantRole(masterManageUsers);
user.setEnabled(true);
user.credentialManager().updateCredential(UserCredentialModel.password("password"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,12 @@ public void testImpersonateByMasterAdmin() {
@Test
public void testImpersonateByMasterImpersonator() {
String userId;
try (Response response = adminClient.realm("master").users().create(UserBuilder.create().username("master-impersonator").build())) {
try (Response response = adminClient.realm("master").users().create(
UserBuilder.create().username("master-impersonator")
.firstName("master-impersonator")
.lastName("master-impersonator")
.email("[email protected]")
.build())) {
userId = ApiUtil.getCreatedId(response);
}

Expand Down Expand Up @@ -195,7 +200,12 @@ public void testImpersonationFailsForDisabledUser() {
@Test
public void testImpersonateByMastertBadImpersonator() {
String userId;
try (Response response = adminClient.realm("master").users().create(UserBuilder.create().username("master-bad-impersonator").build())) {
try (Response response = adminClient.realm("master").users().create(
UserBuilder.create().username("master-bad-impersonator")
.firstName("master-bad-impersonator")
.lastName("master-bad-impersonator")
.email("[email protected]")
.build())) {
userId = ApiUtil.getCreatedId(response);
}
adminClient.realm("master").users().get(userId).resetPassword(CredentialBuilder.create().password("password").build());
Expand Down
Loading

0 comments on commit 7f195ac

Please sign in to comment.