Skip to content

Commit

Permalink
KEYCLOAK-1404: Need recovery mechanism for master admin user
Browse files Browse the repository at this point in the history
  • Loading branch information
ssilvert committed Jun 4, 2015
1 parent ea544c6 commit 6812514
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docbook/reference/en/en-US/master.xml
Expand Up @@ -35,6 +35,7 @@
<!ENTITY UserFederation SYSTEM "modules/user-federation.xml">
<!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
<!ENTITY ExportImport SYSTEM "modules/export-import.xml">
<!ENTITY AdminRecovery SYSTEM "modules/admin-recovery.xml">
<!ENTITY ServerCache SYSTEM "modules/cache.xml">
<!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
<!ENTITY Clustering SYSTEM "modules/clustering.xml">
Expand Down Expand Up @@ -126,6 +127,7 @@ This one is short
&UserFederation;
&Kerberos;
&ExportImport;
&AdminRecovery;
&ServerCache;
&SAML;
&SecurityVulnerabilities;
Expand Down
15 changes: 15 additions & 0 deletions docbook/reference/en/en-US/modules/admin-recovery.xml
@@ -0,0 +1,15 @@
<chapter id="admin-recovery">
<title>Recovering the Master Admin User</title>
<para>
It is possible for the "admin" user in the master realm to become inoperable. This may be because it was
accentally deleted, its role mappings were removed, or the password was simply forgotten.
</para>
<para>
To recover the master admin user, just start the server with the following system property:
<programlisting><![CDATA[
bin/standalone.sh -Dkeycloak.recover-admin=true
]]></programlisting>
Then you can log in to the master admin account with the default password "admin". You will then be
prompted to immediately change this password.
</para>
</chapter>
@@ -0,0 +1,80 @@
/*
* 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;

import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.services.managers.ApplianceBootstrap;

/**
* Static utility class that performs recovery on the master admin account.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class AdminRecovery {
private static final Logger log = Logger.getLogger(AdminRecovery.class);

public static final String RECOVER_ADMIN_ACCOUNT = "keycloak.recover-admin";

// Don't allow instances
private AdminRecovery() {}

public static void recover(KeycloakSessionFactory sessionFactory) {
if (!needRecovery()) return;

KeycloakSession session = sessionFactory.create();

session.getTransaction().begin();
try {
doRecover(session);
session.getTransaction().commit();
log.info("*******************************");
log.info("Recovered Master Admin account.");
log.info("*******************************");
} finally {
session.close();
System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "false");
}
}

private static boolean needRecovery() {
String strNeedRecovery = System.getProperty(RECOVER_ADMIN_ACCOUNT, "false");
return Boolean.parseBoolean(strNeedRecovery);
}

private static void doRecover(KeycloakSession session) {
RealmProvider realmProvider = session.realms();
UserProvider userProvider = session.users();

String adminRealmName = Config.getAdminRealm();
RealmModel realm = realmProvider.getRealmByName(adminRealmName);
UserModel adminUser = userProvider.getUserByUsername("admin", realm);

if (adminUser == null) {
adminUser = userProvider.addUser(realm, "admin");
}

ApplianceBootstrap.setupAdminUser(session, realm, adminUser);
}
}
Expand Up @@ -61,6 +61,10 @@ public void bootstrap(KeycloakSession session, String contextPath) {
KeycloakModelUtils.generateRealmKeys(realm);

UserModel adminUser = session.users().addUser(realm, "admin");
setupAdminUser(session, realm, adminUser);
}

public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser) {
adminUser.setEnabled(true);
UserCredentialModel password = new UserCredentialModel();
password.setType(UserCredentialModel.PASSWORD);
Expand Down
Expand Up @@ -42,6 +42,7 @@
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import org.keycloak.offlineconfig.AdminRecovery;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
Expand Down Expand Up @@ -88,6 +89,7 @@ public KeycloakApplication(@Context ServletContext context, @Context Dispatcher
importRealms(context);
migrateModel();

AdminRecovery.recover(sessionFactory);

setupScheduledTasks(sessionFactory);
}
Expand Down
@@ -0,0 +1,94 @@
/*
* 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.testsuite.offlineconfig;

import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.offlineconfig.AdminRecovery;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebRule;

/**
* Test the AdminRecovery class.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class AdminRecoveryTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();

@Rule
public WebRule webRule = new WebRule(this);

@Test
public void testAdminDeletedRecovery() {
KeycloakSession session = keycloakRule.startSession();
RealmModel masterRealm = session.realms().getRealmByName("master");
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
session.users().removeUser(masterRealm, adminUser);
adminUser = session.users().getUserByUsername("admin", masterRealm);
keycloakRule.stopSession(session, true);

Assert.assertNull(adminUser);

doAdminRecovery(session);

session = keycloakRule.startSession();
adminUser = session.users().getUserByUsername("admin", masterRealm);
Assert.assertNotNull(adminUser);
Assert.assertTrue(adminUser.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD.toString()));
}

@Test
public void testAdminPasswordRecovery() {
KeycloakSession session = keycloakRule.startSession();
RealmModel masterRealm = session.realms().getRealmByName("master");
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
UserCredentialValueModel password = adminUser.getCredentialsDirectly().get(0);
password.setValue("forgotten-password");
adminUser.updateCredentialDirectly(password);
keycloakRule.stopSession(session, true);

Assert.assertEquals("forgotten-password", getAdminPassword());

doAdminRecovery(session);

Assert.assertNotEquals("forgotten-password", getAdminPassword());
}

private void doAdminRecovery(KeycloakSession session) {
System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "true");
AdminRecovery.recover(session.getKeycloakSessionFactory());
}

private String getAdminPassword() {
KeycloakSession session = keycloakRule.startSession();
RealmModel masterRealm = session.realms().getRealmByName("master");
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
UserCredentialValueModel password = adminUser.getCredentialsDirectly().get(0);
keycloakRule.stopSession(session, true);
return password.getValue();
}
}

0 comments on commit 6812514

Please sign in to comment.