${successMessage}
+ <#elseif errorMessage?has_content> +${errorMessage}
+ <#elseif bootstrap> + <#if localUser> +Please create an initial admin user to get started.
+ <#else> ++ You need local access to create the initial admin user. Open http://localhost:8080/auth + or use the add-user script. +
+ #if> + #if> + + <#if bootstrap && localUser> + + #if>Documentation | Administration Console
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java index 5a76153793..5aec527ac8 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java @@ -17,9 +17,8 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.CacheRealmProviderFactory; -import org.keycloak.models.cache.RealmCache; +import org.keycloak.models.cache.entities.CachedClient; import org.keycloak.models.cache.entities.CachedRealm; -import org.keycloak.models.cache.entities.CachedUser; import java.util.concurrent.ConcurrentHashMap; @@ -143,11 +142,23 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa realmLookup.remove(realm.getName()); + for (String r : realm.getRealmRoles().values()) { + realmCache.evictCachedRoleById(r); + } + for (String c : realm.getClients().values()) { realmCache.evictCachedApplicationById(c); } log.tracev("Realm removed realm={0}", realm.getName()); + } else if (object instanceof CachedClient) { + CachedClient client = (CachedClient) object; + + for (String r : client.getRoles().values()) { + realmCache.evictCachedRoleById(r); + } + + log.tracev("Client removed client={0}", client.getId()); } } } diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java index 4cd4f79641..07e2d9af7e 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java @@ -160,6 +160,12 @@ public class InfinispanRealmCache implements RealmCache { cache.remove(id); } + @Override + public void evictCachedRoleById(String id) { + logger.tracev("Evicting role {0}", id); + cache.evict(id); + } + @Override public void addCachedRole(CachedRole role) { if (!enabled) return; diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java index e826ac1715..56f9bc087b 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java @@ -37,6 +37,8 @@ public interface RealmCache { void invalidateRole(CachedRole role); + void evictCachedRoleById(String id); + void addCachedRole(CachedRole role); void invalidateCachedRoleById(String id); diff --git a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java index cedcbddf50..4f94b52b79 100644 --- a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java +++ b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java @@ -2,10 +2,9 @@ package org.keycloak.exportimport; import org.jboss.logging.Logger; -import org.keycloak.Config; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.services.managers.ApplianceBootstrap; + +import java.io.IOException; /** * @author Marek Posolda @@ -14,76 +13,82 @@ public class ExportImportManager { private static final Logger logger = Logger.getLogger(ExportImportManager.class); - public void checkExportImport(KeycloakSessionFactory sessionFactory, String contextPath) { + private KeycloakSession session; + + private final String realmName; + + private ExportProvider exportProvider; + private ImportProvider importProvider; + + public ExportImportManager(KeycloakSession session) { + this.session = session; + + realmName = ExportImportConfig.getRealmName(); + + String providerId = ExportImportConfig.getProvider(); String exportImportAction = ExportImportConfig.getAction(); - String realmName = ExportImportConfig.getRealmName(); - boolean export = false; - boolean importt = false; if (ExportImportConfig.ACTION_EXPORT.equals(exportImportAction)) { - export = true; + exportProvider = session.getProvider(ExportProvider.class, providerId); + if (exportProvider == null) { + throw new RuntimeException("Export provider not found"); + } } else if (ExportImportConfig.ACTION_IMPORT.equals(exportImportAction)) { - importt = true; - } - - if (export || importt) { - String exportImportProviderId = ExportImportConfig.getProvider(); - logger.debug("Will use provider: " + exportImportProviderId); - KeycloakSession session = sessionFactory.create(); - - try { - if (export) { - ExportProvider exportProvider = session.getProvider(ExportProvider.class, exportImportProviderId); - - if (exportProvider == null) { - logger.errorf("Invalid Export Provider %s", exportImportProviderId); - } else { - if (realmName == null) { - logger.info("Full model export requested"); - exportProvider.exportModel(sessionFactory); - } else { - logger.infof("Export of realm '%s' requested", realmName); - exportProvider.exportRealm(sessionFactory, realmName); - } - logger.info("Export finished successfully"); - } - } else { - ImportProvider importProvider = session.getProvider(ImportProvider.class, exportImportProviderId); - - if (importProvider == null) { - logger.errorf("Invalid Import Provider %s", exportImportProviderId); - } else { - - Strategy strategy = ExportImportConfig.getStrategy(); - if (realmName == null) { - logger.infof("Full model import requested. Strategy: %s", strategy.toString()); - - // Check if master realm was exported. If it's not, then it needs to be created before other realms are imported - if (!importProvider.isMasterRealmExported()) { - ApplianceBootstrap.setupDefaultRealm(sessionFactory, contextPath); - ApplianceBootstrap.setupDefaultUser(sessionFactory); - } - - importProvider.importModel(sessionFactory, strategy); - } else { - logger.infof("Import of realm '%s' requested. Strategy: %s", realmName, strategy.toString()); - - if (!realmName.equals(Config.getAdminRealm())) { - // Check if master realm exists. If it's not, then it needs to be created before other realm is imported - ApplianceBootstrap.setupDefaultRealm(sessionFactory, contextPath); - ApplianceBootstrap.setupDefaultUser(sessionFactory); - } - - importProvider.importRealm(sessionFactory, realmName, strategy); - } - logger.info("Import finished successfully"); - } - } - } catch (Throwable ioe) { - logger.error("Error during export/import", ioe); - } finally { - session.close(); + importProvider = session.getProvider(ImportProvider.class, providerId); + if (importProvider == null) { + throw new RuntimeException("Import provider not found"); } } } + + public boolean isRunImport() { + return importProvider != null; + } + + public boolean isImportMasterIncluded() { + if (!isRunImport()) { + throw new IllegalStateException("Import not enabled"); + } + try { + return importProvider.isMasterRealmExported(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public boolean isRunExport() { + return exportProvider != null; + } + + public void runImport() { + try { + Strategy strategy = ExportImportConfig.getStrategy(); + if (realmName == null) { + logger.infof("Full model import requested. Strategy: %s", strategy.toString()); + importProvider.importModel(session.getKeycloakSessionFactory(), strategy); + } else { + logger.infof("Import of realm '%s' requested. Strategy: %s", realmName, strategy.toString()); + importProvider.importRealm(session.getKeycloakSessionFactory(), realmName, strategy); + } + logger.info("Import finished successfully"); + } catch (IOException e) { + throw new RuntimeException("Failed to run import", e); + } + } + + public void runExport() { + try { + if (realmName == null) { + logger.info("Full model export requested"); + exportProvider.exportModel(session.getKeycloakSessionFactory()); + } else { + logger.infof("Export of realm '%s' requested", realmName); + exportProvider.exportRealm(session.getKeycloakSessionFactory(), realmName); + } + logger.info("Export finished successfully"); + } catch (IOException e) { + throw new RuntimeException("Failed to run export"); + } + } + } 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 84020166d7..829dd6c9ac 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java @@ -4,15 +4,7 @@ import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.common.Version; import org.keycloak.common.enums.SslRequired; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.CredentialRepresentation; @@ -23,17 +15,34 @@ import org.keycloak.representations.idm.CredentialRepresentation; public class ApplianceBootstrap { private static final Logger logger = Logger.getLogger(ApplianceBootstrap.class); + private final KeycloakSession session; - public static boolean setupDefaultRealm(KeycloakSessionFactory sessionFactory, String contextPath) { - KeycloakSession session = sessionFactory.create(); - session.getTransaction().begin(); + public ApplianceBootstrap(KeycloakSession session) { + this.session = session; + } + public boolean isNewInstall() { + if (session.realms().getRealms().size() > 0) { + return false; + } else { + return true; + } + } + + public boolean isNoMasterUser() { + RealmModel realm = session.realms().getRealm(Config.getAdminRealm()); + return session.users().getUsersCount(realm) == 0; + } + + public boolean createMasterRealm(String contextPath) { + if (!isNewInstall()) { + throw new IllegalStateException("Can't create default realm as realms already exists"); + } + + KeycloakSession session = this.session.getKeycloakSessionFactory().create(); try { + session.getTransaction().begin(); String adminRealmName = Config.getAdminRealm(); - if (session.realms().getRealm(adminRealmName) != null) { - return false; - } - logger.info("Initializing " + adminRealmName + " realm"); RealmManager manager = new RealmManager(session); @@ -58,41 +67,29 @@ public class ApplianceBootstrap { KeycloakModelUtils.generateRealmKeys(realm); session.getTransaction().commit(); - return true; } finally { session.close(); } + + return true; } - public static boolean setupDefaultUser(KeycloakSessionFactory sessionFactory) { - KeycloakSession session = sessionFactory.create(); - session.getTransaction().begin(); - - try { - RealmModel realm = session.realms().getRealm(Config.getAdminRealm()); - if (session.users().getUserByUsername("admin", realm) == null) { - UserModel adminUser = session.users().addUser(realm, "admin"); - - adminUser.setEnabled(true); - UserCredentialModel usrCredModel = new UserCredentialModel(); - usrCredModel.setType(UserCredentialModel.PASSWORD); - usrCredModel.setValue("admin"); - session.users().updateCredential(realm, adminUser, usrCredModel); - adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - - RoleModel adminRole = realm.getRole(AdminRoles.ADMIN); - adminUser.grantRole(adminRole); - - ClientModel accountApp = realm.getClientNameMap().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); - for (String r : accountApp.getDefaultRoles()) { - adminUser.grantRole(accountApp.getRole(r)); - } - } - session.getTransaction().commit(); - return true; - } finally { - session.close(); + public void createMasterRealmUser(KeycloakSession session, String username, String password) { + RealmModel realm = session.realms().getRealm(Config.getAdminRealm()); + if (session.users().getUsersCount(realm) > 0) { + throw new IllegalStateException("Can't create initial user as users already exists"); } + + UserModel adminUser = session.users().addUser(realm, username); + adminUser.setEnabled(true); + + UserCredentialModel usrCredModel = new UserCredentialModel(); + usrCredModel.setType(UserCredentialModel.PASSWORD); + usrCredModel.setValue(password); + session.users().updateCredential(realm, adminUser, usrCredModel); + + RoleModel adminRole = realm.getRole(AdminRoles.ADMIN); + adminUser.grantRole(adminRole); } } diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 88569b6d2c..2d49464995 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -74,24 +74,51 @@ public class KeycloakApplication extends Application { classes.add(QRCodeResource.class); classes.add(ThemeResource.class); classes.add(JsResource.class); - classes.add(WelcomeResource.class); singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false")))); - boolean defaultRealmCreated = ApplianceBootstrap.setupDefaultRealm(sessionFactory, context.getContextPath()); - migrateModel(); sessionFactory.publish(new PostMigrationEvent()); - new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath()); - importRealms(context); + boolean bootstrapAdminUser = false; - importAddUser(); + KeycloakSession session = sessionFactory.create(); + try { + session.getTransaction().begin(); - if (defaultRealmCreated) { - ApplianceBootstrap.setupDefaultUser(sessionFactory); + ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); + ExportImportManager exportImportManager = new ExportImportManager(session); + + boolean createMasterRealm = applianceBootstrap.isNewInstall(); + if (exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded()) { + createMasterRealm = false; + } + + if (createMasterRealm) { + applianceBootstrap.createMasterRealm(contextPath); + } + + if (exportImportManager.isRunImport()) { + exportImportManager.runImport(); + } else { + importRealms(); + } + + importAddUser(); + + if (exportImportManager.isRunExport()) { + exportImportManager.runExport(); + } + + bootstrapAdminUser = applianceBootstrap.isNoMasterUser(); + + session.getTransaction().commit(); + } finally { + session.close(); } + singletons.add(new WelcomeResource(bootstrapAdminUser)); + setupScheduledTasks(sessionFactory); } @@ -185,34 +212,13 @@ public class KeycloakApplication extends Application { return singletons; } - public void importRealms(ServletContext context) { - importRealmFile(); - importRealmResources(context); - } - - public void importRealmResources(ServletContext context) { - String resources = context.getInitParameter("keycloak.import.realm.resources"); - if (resources != null) { - StringTokenizer tokenizer = new StringTokenizer(resources, ","); - while (tokenizer.hasMoreTokens()) { - String resource = tokenizer.nextToken().trim(); - InputStream is = context.getResourceAsStream(resource); - if (is == null) { - log.warn("Could not find realm resource to import: " + resource); - } - RealmRepresentation rep = loadJson(is, RealmRepresentation.class); - importRealm(rep, "resource " + resource); - } - } - } - - public void importRealmFile() { + public void importRealms() { String files = System.getProperty("keycloak.import"); if (files != null) { StringTokenizer tokenizer = new StringTokenizer(files, ","); while (tokenizer.hasMoreTokens()) { String file = tokenizer.nextToken().trim(); - RealmRepresentation rep = null; + RealmRepresentation rep; try { rep = loadJson(new FileInputStream(file), RealmRepresentation.class); } catch (FileNotFoundException e) { diff --git a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java index 7aa6b017bf..e778de3b00 100755 --- a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java +++ b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java @@ -2,22 +2,24 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.keycloak.Config; +import org.keycloak.freemarker.FreeMarkerUtil; import org.keycloak.freemarker.Theme; import org.keycloak.freemarker.ThemeProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.common.util.MimeTypeUtil; +import org.keycloak.services.managers.ApplianceBootstrap; +import org.keycloak.services.util.CacheControlUtil; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.CacheControl; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import java.io.IOException; import java.io.InputStream; +import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; /** * @author Stian Thorgersen @@ -27,12 +29,18 @@ public class WelcomeResource { private static final Logger logger = Logger.getLogger(WelcomeResource.class); + private boolean bootstrap; + @Context private UriInfo uriInfo; @Context protected KeycloakSession session; + public WelcomeResource(boolean bootstrap) { + this.bootstrap = bootstrap; + } + /** * Welcome page of Keycloak * @@ -42,11 +50,56 @@ public class WelcomeResource { @GET @Produces("text/html") public Response getWelcomePage() throws URISyntaxException { + checkBootstrap(); + String requestUri = uriInfo.getRequestUri().toString(); if (!requestUri.endsWith("/")) { return Response.seeOther(new URI(requestUri + "/")).build(); } else { - return getResource("index.html"); + return createWelcomePage(null, null); + } + } + + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response createUser(final MultivaluedMap