diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index d1e7ac6999..be76cc913c 100755 --- a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -66,19 +66,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon } else { initEmbedded(); } - - // Backwards compatibility - if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) { - logger.debugf("No configuration provided for '%s' cache. Using '%s' configuration as template", - InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME); - - Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME); - if (sessionCacheConfig != null) { - ConfigurationBuilder confBuilder = new ConfigurationBuilder().read(sessionCacheConfig); - Configuration offlineSessionConfig = confBuilder.build(); - cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionConfig); - } - } } } } @@ -139,6 +126,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon Configuration sessionCacheConfiguration = sessionConfigBuilder.build(); cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration); + cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration); cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration); } diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone-ha.xsl b/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone-ha.xsl index 31f681fd77..c0680719c5 100755 --- a/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone-ha.xsl +++ b/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone-ha.xsl @@ -43,6 +43,7 @@ + diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone.xsl b/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone.xsl index 3d4b77cedb..403fe346d6 100755 --- a/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone.xsl +++ b/distribution/server-overlay/eap6/eap6-server-overlay/src/main/xslt/standalone.xsl @@ -51,6 +51,7 @@ + diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml index 01ad7e6c4a..4dc4b323b8 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml @@ -130,15 +130,14 @@ cd <WILDFLY_HOME>/bin
Admin User - To access the admin console you need an account to login. Currently, there's a default account added - with the username admin and password admin. You will be required - to change the password on first login. We are planning on removing the built-in account soon and will - instead have an initial step to create the user. + To access the admin console to configure Keycloak you need an account to login. There is no built in user, + instead you have to first create an admin account. This can done either by opening http://localhost:8080/auth + (creating a user through the browser can only be done through localhost) or you can use the add-user script from + the command-line. - You can also create a user with the add-user script found in bin. - This script will create a temporary file with the details of the user, which are imported at startup. - To add a user with this script run: + The add-user script creates a temporary file with the details of the user, + which are imported at startup. To add a user with this script run: -p ]]> diff --git a/forms/common-themes/src/main/resources/theme/keycloak/welcome/resources/index.html b/forms/common-themes/src/main/resources/theme/keycloak/welcome/index.ftl similarity index 55% rename from forms/common-themes/src/main/resources/theme/keycloak/welcome/resources/index.html rename to forms/common-themes/src/main/resources/theme/keycloak/welcome/index.ftl index dd0b909bae..9a6c51c90a 100755 --- a/forms/common-themes/src/main/resources/theme/keycloak/welcome/resources/index.html +++ b/forms/common-themes/src/main/resources/theme/keycloak/welcome/index.ftl @@ -26,6 +26,28 @@ Welcome to Keycloak + @@ -36,7 +58,41 @@

Welcome to Keycloak

-

Your Keycloak is running.

+ <#if successMessage?has_content> +

${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 bootstrap && localUser> +
+

+ + +

+ +

+ + +

+ +

+ + +

+ + +
+

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 formData) { + checkBootstrap(); + + if (!bootstrap) { + return createWelcomePage(null, null); + } else { + if (!isLocal()) { + logger.errorv("Rejected non-local attempt to create initial user from {0}", session.getContext().getConnection().getRemoteAddr()); + throw new WebApplicationException(Response.Status.BAD_REQUEST); + } + + String username = formData.getFirst("username"); + String password = formData.getFirst("password"); + String passwordConfirmation = formData.getFirst("passwordConfirmation"); + + if (username == null || username.length() == 0) { + return createWelcomePage(null, "Username is missing"); + } + + if (password == null || password.length() == 0) { + return createWelcomePage(null, "Password is missing"); + } + + if (!password.equals(passwordConfirmation)) { + return createWelcomePage(null, "Password and confirmation doesn't match"); + } + + ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); + if (applianceBootstrap.isNoMasterUser()) { + bootstrap = false; + applianceBootstrap.createMasterRealmUser(session, username, password); + + logger.infov("Created initial admin user with username {0}", username); + return createWelcomePage("User created", null); + } else { + logger.warnv("Rejected attempt to create initial user as user is already created"); + return createWelcomePage(null, "Users already exists"); + } } } @@ -61,26 +114,62 @@ public class WelcomeResource { @Produces("text/html") public Response getResource(@PathParam("path") String path) { try { - Config.Scope config = Config.scope("theme"); - - ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); - Theme theme = themeProvider.getTheme(config.get("welcomeTheme"), Theme.Type.WELCOME); - InputStream resource = theme.getResourceAsStream(path); + InputStream resource = getTheme().getResourceAsStream(path); if (resource != null) { String contentType = MimeTypeUtil.getContentType(path); - - CacheControl cacheControl = new CacheControl(); - cacheControl.setNoTransform(false); - cacheControl.setMaxAge(config.getInt("staticMaxAge", -1)); - - Response.ResponseBuilder builder = Response.ok(resource).type(contentType).cacheControl(cacheControl); + Response.ResponseBuilder builder = Response.ok(resource).type(contentType).cacheControl(CacheControlUtil.getDefaultCacheControl()); return builder.build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); } + } catch (IOException e) { + throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); + } + } + + private Response createWelcomePage(String successMessage, String errorMessage) { + try { + Map map = new HashMap<>(); + map.put("bootstrap", bootstrap); + if (bootstrap) { + map.put("localUser", isLocal()); + } + if (successMessage != null) { + map.put("successMessage", successMessage); + } + if (errorMessage != null) { + map.put("errorMessage", errorMessage); + } + FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); + String result = freeMarkerUtil.processTemplate(map, "index.ftl", getTheme()); + return Response.status(errorMessage == null ? Response.Status.OK : Response.Status.BAD_REQUEST).entity(result).cacheControl(CacheControlUtil.noCache()).build(); } catch (Exception e) { - logger.warn("Failed to get theme resource", e); - return Response.serverError().build(); + throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); + } + } + + private Theme getTheme() { + Config.Scope config = Config.scope("theme"); + ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); + try { + return themeProvider.getTheme(config.get("welcomeTheme"), Theme.Type.WELCOME); + } catch (IOException e) { + throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); + } + } + + private void checkBootstrap() { + if (bootstrap) { + bootstrap = new ApplianceBootstrap(session).isNoMasterUser(); + } + } + + private boolean isLocal() { + try { + InetAddress inetAddress = InetAddress.getByName(session.getContext().getConnection().getRemoteAddr()); + return inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress(); + } catch (UnknownHostException e) { + throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 5c22200a41..50e33ddc96 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -264,10 +264,7 @@ public class AdminConsole { if (!uriInfo.getRequestUri().getPath().endsWith("/")) { return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build(); } else { - String adminTheme = realm.getAdminTheme(); - if (adminTheme == null) { - adminTheme = "keycloak"; - } + Theme theme = getTheme(); Map map = new HashMap<>(); @@ -277,11 +274,8 @@ public class AdminConsole { authUrl = authUrl.substring(0, authUrl.length() - 1); map.put("authUrl", authUrl); - map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + adminTheme); + map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + theme.getName()); map.put("resourceVersion", Version.RESOURCES_VERSION); - - Theme theme = getTheme(); - map.put("properties", theme.getProperties()); FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 038f43b568..9d144e4c2d 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -264,8 +264,6 @@ public class RealmAdminResource { if (!new RealmManager(session).removeRealm(realm)) { throw new NotFoundException("Realm doesn't exist"); - } else { - clearAdminEvents(); } } diff --git a/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java b/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java index 259083a2d4..0e2f1e0c97 100644 --- a/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java +++ b/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java @@ -19,7 +19,12 @@ public class CacheControlUtil { cacheControl.setNoCache(true); } return cacheControl; + } + public static CacheControl noCache() { + CacheControl cacheControl = new CacheControl(); + cacheControl.setNoCache(true); + return cacheControl; } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java new file mode 100644 index 0000000000..3adbbac92e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java @@ -0,0 +1,44 @@ +package org.keycloak.testsuite.auth.page; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * @author Stian Thorgersen + */ +public class WelcomePage extends AuthServer { + + @FindBy(id = "username") + private WebElement usernameInput; + + @FindBy(id = "password") + private WebElement passwordInput; + + @FindBy(id = "passwordConfirmation") + private WebElement passwordConfirmationInput; + + @FindBy(id = "create-button") + private WebElement createButton; + + public boolean isPasswordSet() { + return !driver.getPageSource().contains("Please create an initial admin user to get started."); + } + + public void setPassword(String username, String password) { + usernameInput.clear(); + usernameInput.sendKeys(username); + + passwordInput.clear(); + passwordInput.sendKeys(password); + + passwordConfirmationInput.clear(); + passwordConfirmationInput.sendKeys(password); + + createButton.click(); + + if (!driver.getPageSource().contains("User created")) { + throw new RuntimeException("Failed to updated password"); + } + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 063347a7ea..13ec96e4d6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -20,6 +20,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import static org.keycloak.testsuite.admin.Users.setPasswordFor; import org.keycloak.testsuite.arquillian.SuiteContext; +import org.keycloak.testsuite.auth.page.WelcomePage; import org.keycloak.testsuite.util.OAuthClient; import org.openqa.selenium.WebDriver; import org.keycloak.testsuite.auth.page.AuthServer; @@ -76,6 +77,9 @@ public abstract class AbstractKeycloakTest { @Page protected UpdatePassword updatePasswordPage; + @Page + protected WelcomePage welcomePage; + protected UserRepresentation adminUser; @Before @@ -103,11 +107,10 @@ public abstract class AbstractKeycloakTest { } private void updateMasterAdminPassword() { - accountPage.navigateTo(); - loginPage.form().login(ADMIN, ADMIN); - updatePasswordPage.updatePasswords(ADMIN, ADMIN); - assertCurrentUrlStartsWith(accountPage); - deleteAllCookiesForMasterRealm(); + welcomePage.navigateTo(); + if (!welcomePage.isPasswordSet()) { + welcomePage.setPassword("admin", "admin"); + } } public void deleteAllCookiesForMasterRealm() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java index 28f0915189..2466f3621d 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java @@ -33,10 +33,10 @@ import org.jboss.resteasy.spi.ResteasyDeployment; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.filters.ClientConnectionFilter; import org.keycloak.services.filters.KeycloakSessionServletFilter; +import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.util.JsonSerialization; @@ -273,19 +273,17 @@ public class KeycloakServer { } protected void setupDevConfig() { - KeycloakSession session = sessionFactory.create(); - session.getTransaction().begin(); - - try { - RealmManager manager = new RealmManager(session); - - RealmModel adminRealm = manager.getKeycloakAdminstrationRealm(); - UserModel admin = session.users().getUserByUsername("admin", adminRealm); - admin.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - - session.getTransaction().commit(); - } finally { - session.close(); + if (System.getProperty("keycloak.createAdminUser", "true").equals("true")) { + KeycloakSession session = sessionFactory.create(); + try { + session.getTransaction().begin(); + if (new ApplianceBootstrap(session).isNoMasterUser()) { + new ApplianceBootstrap(session).createMasterRealmUser(session, "admin", "admin"); + } + session.getTransaction().commit(); + } finally { + session.close(); + } } } @@ -311,7 +309,6 @@ public class KeycloakServer { di.setDefaultEncoding("UTF-8"); di.setDefaultServletConfig(new DefaultServletConfig(true)); - di.addWelcomePage("theme/keycloak/welcome/resources/index.html"); FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class); di.addFilter(filter); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java index 604dcffca4..b2fb326c5d 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java @@ -3,6 +3,9 @@ package org.keycloak.testsuite.admin; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ServerInfoResource; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.representations.idm.ClientRepresentation; @@ -90,6 +93,14 @@ public class RealmTest extends AbstractClientTest { assertNames(keycloak.realms().findAll(), "master", "test"); } + @Test + public void loginAfterRemoveRealm() { + realm.remove(); + + ServerInfoResource serverInfoResource = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).serverInfo(); + serverInfoResource.getInfo(); + } + @Test public void updateRealm() { // first change diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java index a69b733ab6..b8002d3f67 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java @@ -331,6 +331,48 @@ public class RefreshTokenTest { } } + @Test + public void refreshTokenClientDisabled() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + + Event loginEvent = events.expectLogin().assertEvent(); + + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + String refreshTokenString = response.getRefreshToken(); + RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString); + + events.expectCodeToToken(codeId, sessionId).assertEvent(); + + try { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.getClientByClientId(oauth.getClientId()).setEnabled(false); + } + }); + + response = oauth.doRefreshTokenRequest(refreshTokenString, "password"); + + assertEquals(400, response.getStatusCode()); + assertEquals("invalid_client", response.getError()); + + events.expectRefresh(refreshToken.getId(), sessionId).user((String) null).session((String) null).clearDetails().error(Errors.CLIENT_DISABLED).assertEvent(); + } finally { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.getClientByClientId(oauth.getClientId()).setEnabled(true); + } + }); + + } + } + @Test public void refreshTokenUserSessionExpired() { oauth.doLogin("test-user@localhost", "password"); diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java index 150dbbd1d5..1c255e91e0 100644 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java @@ -60,6 +60,7 @@ public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcesso st.addDependency(cacheContainerService.append("realms")); st.addDependency(cacheContainerService.append("users")); st.addDependency(cacheContainerService.append("sessions")); + st.addDependency(cacheContainerService.append("offlineSessions")); st.addDependency(cacheContainerService.append("loginFailures")); } } diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml index 1d92afc6d5..5da36aa969 100644 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml @@ -11,6 +11,7 @@ + @@ -59,6 +60,7 @@ +