diff --git a/docbook/reference/en/en-US/modules/admin-recovery.xml b/docbook/reference/en/en-US/modules/admin-recovery.xml index e026cddecd..941284805f 100755 --- a/docbook/reference/en/en-US/modules/admin-recovery.xml +++ b/docbook/reference/en/en-US/modules/admin-recovery.xml @@ -5,11 +5,11 @@ accidentally deleted, its role mappings were removed, or the password was simply forgotten. - To recover the master admin user, just start the server with the following system property: + To recover the master admin user, just start the server with the following system properties: - Then you can log in to the master admin account with the default password "admin". You will then be + Then you can log in to the master admin account with your temporary password. You will then be prompted to immediately change this password. \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/offlineconfig/AdminRecovery.java b/services/src/main/java/org/keycloak/offlineconfig/AdminRecovery.java index 0f384ce5d7..cb775b1b5f 100644 --- a/services/src/main/java/org/keycloak/offlineconfig/AdminRecovery.java +++ b/services/src/main/java/org/keycloak/offlineconfig/AdminRecovery.java @@ -36,6 +36,7 @@ public class AdminRecovery { private static final Logger log = Logger.getLogger(AdminRecovery.class); public static final String RECOVER_ADMIN_ACCOUNT = "keycloak.recover-admin"; + public static final String TEMP_ADMIN_PASSWORD = "keycloak.temp-admin-password"; // Don't allow instances private AdminRecovery() {} @@ -47,14 +48,15 @@ public class AdminRecovery { session.getTransaction().begin(); try { - doRecover(session); + doRecover(session, getTempAdminPassword()); session.getTransaction().commit(); log.info("*******************************"); log.info("Recovered Master Admin account."); log.info("*******************************"); } finally { session.close(); - System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "false"); + System.clearProperty(RECOVER_ADMIN_ACCOUNT); + System.clearProperty(TEMP_ADMIN_PASSWORD); } } @@ -63,7 +65,15 @@ public class AdminRecovery { return Boolean.parseBoolean(strNeedRecovery); } - private static void doRecover(KeycloakSession session) { + private static String getTempAdminPassword() { + String tempAdminPassword = System.getProperty(TEMP_ADMIN_PASSWORD); + if ((tempAdminPassword == null) || tempAdminPassword.isEmpty()) { + throw new OfflineConfigException("Must provide temporary admin password to recover admin account."); + } + return tempAdminPassword; + } + + private static void doRecover(KeycloakSession session, String tempAdminPassword) { RealmProvider realmProvider = session.realms(); UserProvider userProvider = session.users(); @@ -75,6 +85,6 @@ public class AdminRecovery { adminUser = userProvider.addUser(realm, "admin"); } - ApplianceBootstrap.setupAdminUser(session, realm, adminUser); + ApplianceBootstrap.setupAdminUser(session, realm, adminUser, tempAdminPassword); } } diff --git a/services/src/main/java/org/keycloak/offlineconfig/OfflineConfigException.java b/services/src/main/java/org/keycloak/offlineconfig/OfflineConfigException.java new file mode 100644 index 0000000000..09a4a5ca6d --- /dev/null +++ b/services/src/main/java/org/keycloak/offlineconfig/OfflineConfigException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.offlineconfig; + +/** + * Runtime exception thrown when an offline configuration fails. Offline + * configuration is defined as any configuration done before the Keycloak Server + * starts accepting requests. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. + */ +public class OfflineConfigException extends IllegalStateException { + + public OfflineConfigException(String msg) { + super(msg); + } +} diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java index fbd6ea5309..7510572c79 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java @@ -61,15 +61,15 @@ public class ApplianceBootstrap { KeycloakModelUtils.generateRealmKeys(realm); UserModel adminUser = session.users().addUser(realm, "admin"); - setupAdminUser(session, realm, adminUser); + setupAdminUser(session, realm, adminUser, "admin"); } - public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser) { + public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser, String password) { adminUser.setEnabled(true); - UserCredentialModel password = new UserCredentialModel(); - password.setType(UserCredentialModel.PASSWORD); - password.setValue("admin"); - session.users().updateCredential(realm, adminUser, password); + UserCredentialModel usrCredModel = new UserCredentialModel(); + usrCredModel.setType(UserCredentialModel.PASSWORD); + usrCredModel.setValue(password); + session.users().updateCredential(realm, adminUser, usrCredModel); adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); RoleModel adminRole = realm.getRole(AdminRoles.ADMIN); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/offlineconfig/AdminRecoveryTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/offlineconfig/AdminRecoveryTest.java index b8aebbb2df..7e070dd607 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/offlineconfig/AdminRecoveryTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/offlineconfig/AdminRecoveryTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.offlineconfig; +import org.junit.After; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Rule; @@ -27,6 +28,7 @@ import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.offlineconfig.AdminRecovery; +import org.keycloak.offlineconfig.OfflineConfigException; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.WebRule; @@ -42,6 +44,13 @@ public class AdminRecoveryTest { @Rule public WebRule webRule = new WebRule(this); + // Verifies that system properties were cleared at the end of recovery + @After + public void verifySysPropsCleared() { + Assert.assertNull(System.getProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT)); + Assert.assertNull(System.getProperty(AdminRecovery.TEMP_ADMIN_PASSWORD)); + } + @Test public void testAdminDeletedRecovery() { KeycloakSession session = keycloakRule.startSession(); @@ -78,8 +87,16 @@ public class AdminRecoveryTest { Assert.assertNotEquals("forgotten-password", getAdminPassword()); } + @Test(expected = OfflineConfigException.class) + public void testAdminRecoveryWithoutPassword() { + KeycloakSession session = keycloakRule.startSession(); + System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "true"); + AdminRecovery.recover(session.getKeycloakSessionFactory()); + } + private void doAdminRecovery(KeycloakSession session) { System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "true"); + System.setProperty(AdminRecovery.TEMP_ADMIN_PASSWORD, "foo"); AdminRecovery.recover(session.getKeycloakSessionFactory()); }