keycloak-developers

Merge upstream/master into migration

10/27/2015 8:50:30 AM

Changes

examples/pom.xml 18(+11 -7)

Details

diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 60b0635..401cf74 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
 
     public String FIRST_VERSION = "1.0.0.Final";
 
-    public String LAST_VERSION = "1.6.0";
+    public String LAST_VERSION = "1.6.1";
 
     public String getCurrentVersionSql(String defaultSchema);
 
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
index b50a4f4..5b0a0da 100644
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
@@ -31,7 +31,7 @@
                 <constraints nullable="false"/>
             </column>
             <column name="LAST_SESSION_REFRESH" type="INT"/>
-            <column name="OFFLINE_FLAG" type="VARCHAR(4)">
+            <column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
                 <constraints nullable="false"/>
             </column>
             <column name="DATA" type="CLOB"/>
@@ -47,14 +47,14 @@
             <column name="CLIENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="OFFLINE_FLAG" type="VARCHAR(4)">
+            <column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
                 <constraints nullable="false"/>
             </column>
             <column name="TIMESTAMP" type="INT"/>
             <column name="DATA" type="CLOB"/>
         </createTable>
 
-        <addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
-        <addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
+        <addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
+        <addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.1.xml
new file mode 100644
index 0000000..4e0129e
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.1.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+    <changeSet author="mposolda@redhat.com" id="1.6.1_from15">
+
+        <preConditions onFail="MARK_RAN" onFailMessage="Upgrading from 1.6.0 version. Skipped 1.6.1_from15 changeSet and marked as ran">
+            <not>
+                <changeSetExecuted id="1.6.0" author="mposolda@redhat.com" changeLogFile="META-INF/jpa-changelog-1.6.0.xml" />
+            </not>
+        </preConditions>
+
+        <addColumn tableName="REALM">
+            <column name="OFFLINE_SESSION_IDLE_TIMEOUT" type="INT" defaultValueNumeric="0"/>
+            <column name="REVOKE_REFRESH_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+
+        <addColumn tableName="KEYCLOAK_ROLE">
+            <column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+
+        <addColumn tableName="CLIENT">
+            <column name="ROOT_URL" type="VARCHAR(255)"/>
+            <column name="DESCRIPTION" type="VARCHAR(255)"/>
+        </addColumn>
+
+        <createTable tableName="OFFLINE_USER_SESSION">
+            <column name="USER_SESSION_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="USER_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="LAST_SESSION_REFRESH" type="INT"/>
+            <column name="OFFLINE_FLAG" type="VARCHAR(4)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="DATA" type="CLOB"/>
+        </createTable>
+
+        <createTable tableName="OFFLINE_CLIENT_SESSION">
+            <column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="USER_SESSION_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="CLIENT_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="OFFLINE_FLAG" type="VARCHAR(4)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="TIMESTAMP" type="INT"/>
+            <column name="DATA" type="CLOB"/>
+        </createTable>
+
+        <addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_US_SES_PK2" tableName="OFFLINE_USER_SESSION"/>
+        <addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION"/>
+    </changeSet>
+
+    <!-- Just for the update from 1.6.0 -->
+    <changeSet author="mposolda@redhat.com" id="1.6.1_from16">
+
+        <preConditions onFail="MARK_RAN" onFailMessage="Upgrading from 1.5.0 or older version. Skipped 1.6.1_from16 changeSet and marked as ran">
+            <changeSetExecuted id="1.6.0" author="mposolda@redhat.com" changeLogFile="META-INF/jpa-changelog-1.6.0.xml" />
+        </preConditions>
+
+        <dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION" />
+        <dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION" />
+
+        <addColumn tableName="OFFLINE_USER_SESSION">
+            <column name="OFFLINE_FLAG" type="VARCHAR(4)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <update tableName="OFFLINE_USER_SESSION">
+            <column name="OFFLINE_FLAG" value="1"/>
+        </update>
+        <dropColumn tableName="OFFLINE_USER_SESSION" columnName="OFFLINE" />
+
+        <addColumn tableName="OFFLINE_CLIENT_SESSION">
+            <column name="OFFLINE_FLAG" type="VARCHAR(4)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <update tableName="OFFLINE_CLIENT_SESSION">
+            <column name="OFFLINE_FLAG" value="1"/>
+        </update>
+        <dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="OFFLINE" />
+
+        <addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_US_SES_PK2" tableName="OFFLINE_USER_SESSION"/>
+        <addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION"/>
+
+    </changeSet>
+
+    <changeSet author="mposolda@redhat.com" id="1.6.1">
+    </changeSet>
+
+</databaseChangeLog>
\ No newline at end of file
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
index 6cd96c6..3010118 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -9,5 +9,5 @@
     <include file="META-INF/jpa-changelog-1.3.0.xml"/>
     <include file="META-INF/jpa-changelog-1.4.0.xml"/>
     <include file="META-INF/jpa-changelog-1.5.0.xml"/>
-    <include file="META-INF/jpa-changelog-1.6.0.xml"/>
+    <include file="META-INF/jpa-changelog-1.6.1.xml"/>
 </databaseChangeLog>
diff --git a/distribution/saml-adapters/jetty91-adapter-zip/pom.xml b/distribution/saml-adapters/jetty91-adapter-zip/pom.xml
index 2c15be4..733aabf 100755
--- a/distribution/saml-adapters/jetty91-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/jetty91-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.6.0.Final-SNAPSHOT</version>
+        <version></version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
index 33d62dc..2036afb 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
@@ -210,6 +210,7 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp'
                         <listitem>prompt - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed)</listitem>
                         <listitem>loginHint - used to pre-fill the username/email field on the login form</listitem>
                         <listitem>action - if value is 'register' then user is redirected to registration page, otherwise to login page</listitem>
+                        <listitem>locale - specifies the desired locale for the UI</listitem>
                     </itemizedlist>
                 </para>
             </simplesect>

examples/pom.xml 18(+11 -7)

diff --git a/examples/pom.xml b/examples/pom.xml
index ec25be9..6423a7c 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -14,16 +14,20 @@
     <packaging>pom</packaging>
 
     <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <configuration>
+                        <skip>true</skip>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-deploy-plugin</artifactId>
-                <configuration>
-                    <skip>true</skip>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
                 <configuration>
                     <failOnMissingWebXml>false</failOnMissingWebXml>
diff --git a/examples/saml/post-with-encryption/pom.xml b/examples/saml/post-with-encryption/pom.xml
index 0c95d95..090f6af 100755
--- a/examples/saml/post-with-encryption/pom.xml
+++ b/examples/saml/post-with-encryption/pom.xml
@@ -2,9 +2,13 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak.examples</groupId>
+    <parent>
+        <artifactId>keycloak-examples-saml-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
     <artifactId>saml-post-encryption</artifactId>
-    <version>1.6.0.Final-SNAPSHOT</version>
 
     <packaging>war</packaging>
 
diff --git a/examples/saml/post-with-signature/pom.xml b/examples/saml/post-with-signature/pom.xml
index 8841ac0..14db058 100755
--- a/examples/saml/post-with-signature/pom.xml
+++ b/examples/saml/post-with-signature/pom.xml
@@ -2,9 +2,13 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak.examples</groupId>
+    <parent>
+        <artifactId>keycloak-examples-saml-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
     <artifactId>saml-post-signatures</artifactId>
-    <version>1.6.0.Final-SNAPSHOT</version>
 
     <packaging>war</packaging>
 
diff --git a/examples/saml/redirect-with-signature/pom.xml b/examples/saml/redirect-with-signature/pom.xml
index 55f426b..06a4e55 100755
--- a/examples/saml/redirect-with-signature/pom.xml
+++ b/examples/saml/redirect-with-signature/pom.xml
@@ -2,9 +2,13 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak.examples</groupId>
+    <parent>
+        <artifactId>keycloak-examples-saml-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
     <artifactId>saml-redirect-signatures</artifactId>
-    <version>1.6.0.Final-SNAPSHOT</version>
 
     <packaging>war</packaging>
 
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-reset-password.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-reset-password.ftl
index e19a266..404de17 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-reset-password.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-reset-password.ftl
@@ -11,7 +11,7 @@
                     <label for="username" class="${properties.kcLabelClass!}">${msg("usernameOrEmail")}</label>
                 </div>
                 <div class="${properties.kcInputWrapperClass!}">
-                    <input type="text" id="username" name="username" class="${properties.kcInputClass!}" />
+                    <input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus/>
                 </div>
             </div>
 
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
index e472fff..3f46b76 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
@@ -12,7 +12,7 @@
                 </div>
 
                 <div class="${properties.kcInputWrapperClass!}">
-                    <input id="totp" name="totp" type="text" class="${properties.kcInputClass!}" />
+                    <input id="totp" name="totp" type="text" class="${properties.kcInputClass!}" autofocus />
                 </div>
             </div>
 
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index cb5f2be..fccfce1 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -52,6 +52,8 @@ public interface LoginFormsProvider extends Provider {
 
     public LoginFormsProvider setClientSessionCode(String accessCode);
 
+    public LoginFormsProvider setClientSession(ClientSessionModel clientSession);
+
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappers);
     public LoginFormsProvider setAccessRequest(String message);
 
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 7fc1bcd..6125d0b 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -47,6 +47,7 @@ import org.keycloak.login.freemarker.model.TotpBean;
 import org.keycloak.login.freemarker.model.UrlBean;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -138,7 +139,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             case VERIFY_EMAIL:
                 try {
                     UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
-                    builder.queryParam("key", accessCode);
+                    builder.queryParam(OAuth2Constants.CODE, accessCode);
+                    builder.queryParam("key", clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
 
                     String link = builder.build(realm.getName()).toString();
                     long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@@ -532,6 +534,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public LoginFormsProvider setClientSession(ClientSessionModel clientSession) {
+        this.clientSession = clientSession;
+        return this;
+    }
+
+    @Override
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappersRequested) {
         this.realmRolesRequested = realmRolesRequested;
         this.resourceRolesRequested = resourceRolesRequested;
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ClientBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ClientBean.java
index f10d031..a292718 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ClientBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ClientBean.java
@@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.services.util.ResolveRelative;
 
 import java.net.URI;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -32,4 +33,11 @@ public class ClientBean {
         return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
     }
 
+    public Map<String,String> getAttributes(){
+        return client.getAttributes();
+    }
+
+    public String getAttribute(String key){
+        return client.getAttribute(key);
+    }
 }
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index d189a36..7b378de 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -168,6 +168,10 @@
                 url += '&scope=' + options.scope;
             }
 
+            if (options && options.locale) {
+                url += '&ui_locales=' + options.locale;
+            }
+
             return url;
         }
 
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
index 88817ac..45c73a3 100644
--- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
@@ -63,10 +63,8 @@ public class MigrateTo1_6_0 {
                 KeycloakModelUtils.setupOfflineTokens(realm);
                 RoleModel role = realm.getRole(Constants.OFFLINE_ACCESS_ROLE);
 
-                // Check if possible to avoid iterating over users
-                for (UserModel user : session.userStorage().getUsers(realm, true)) {
-                    user.grantRole(role);
-                }
+                // Bulk grant of offline_access role to all users
+                session.users().grantToAllUsers(realm, role);
             }
 
             ClientModel adminConsoleClient = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 43bdc7d..8977def 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -22,4 +22,6 @@ public interface Constants {
 
     // 30 days
     int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
+
+    public static final String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index bea51e0..cde5eb9 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -334,6 +334,12 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
+    public void grantToAllUsers(RealmModel realm, RoleModel role) {
+        // not federation-aware for now
+        session.userStorage().grantToAllUsers(realm, role);
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
             UserFederationProvider fed = getFederationProvider(federation);
diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java
index 7d7064d..82be2fe 100755
--- a/model/api/src/main/java/org/keycloak/models/UserProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java
@@ -43,6 +43,8 @@ public interface UserProvider extends Provider {
     Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
     FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
 
+    void grantToAllUsers(RealmModel realm, RoleModel role);
+
     void preRemove(RealmModel realm);
 
     void preRemove(RealmModel realm, UserFederationProviderModel link);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java b/model/api/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
new file mode 100644
index 0000000..513836f
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
@@ -0,0 +1,11 @@
+package org.keycloak.models.utils;
+
+import org.keycloak.provider.ProviderEvent;
+
+/**
+ * Executed at startup after model migration is finished
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PostMigrationEvent implements ProviderEvent {
+}
diff --git a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
index 8edfe3e..8c24262 100755
--- a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
+++ b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
@@ -423,6 +423,13 @@ public class FileUserProvider implements UserProvider {
     }
 
     @Override
+    public void grantToAllUsers(RealmModel realm, RoleModel role) {
+        for (UserModel user : inMemoryModel.getUsers(realm.getId())) {
+            user.grantRole(role);
+        }
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         // Nothing to do here?  Federation links are attached to users, which are removed by InMemoryModel
     }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
index 69fc5cf..9045f91 100644
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
@@ -304,6 +304,12 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
     }
 
     @Override
+    public void grantToAllUsers(RealmModel realm, RoleModel role) {
+        realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
+        getDelegate().grantToAllUsers(realm, role);
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         realmInvalidations.add(realm.getId());
         getDelegate().preRemove(realm);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java
index 1ce81e8..45dfc3d 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java
@@ -23,7 +23,8 @@ import java.io.Serializable;
         @NamedQuery(name="deleteUserRoleMappingsByRealm", query="delete from  UserRoleMappingEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"),
         @NamedQuery(name="deleteUserRoleMappingsByRealmAndLink", query="delete from  UserRoleMappingEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
         @NamedQuery(name="deleteUserRoleMappingsByRole", query="delete from UserRoleMappingEntity m where m.roleId = :roleId"),
-        @NamedQuery(name="deleteUserRoleMappingsByUser", query="delete from UserRoleMappingEntity m where m.user = :user")
+        @NamedQuery(name="deleteUserRoleMappingsByUser", query="delete from UserRoleMappingEntity m where m.user = :user"),
+        @NamedQuery(name="grantRoleToAllUsers", query="insert into UserRoleMappingEntity (roleId, user) select role.id, user from RoleEntity role, UserEntity user where role.id = :roleId AND role.realm.id = :realmId AND user.realmId = :realmId")
 
 })
 @Table(name="USER_ROLE_MAPPING")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index d4d533a..c903147 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -147,7 +147,13 @@ public class JpaUserProvider implements UserProvider {
         }
     }
 
-
+    @Override
+    public void grantToAllUsers(RealmModel realm, RoleModel role) {
+        int num = em.createNamedQuery("grantRoleToAllUsers")
+                .setParameter("realmId", realm.getId())
+                .setParameter("roleId", role.getId())
+                .executeUpdate();
+    }
 
     @Override
     public void preRemove(RealmModel realm) {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 358e6f2..9fc9735 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -369,6 +369,19 @@ public class MongoUserProvider implements UserProvider {
     }
 
     @Override
+    public void grantToAllUsers(RealmModel realm, RoleModel role) {
+        DBObject query = new QueryBuilder()
+                .and("realmId").is(realm.getId())
+                .get();
+
+        DBObject update = new QueryBuilder()
+                .and("$push").is(new BasicDBObject("roleIds", role.getId()))
+                .get();
+
+        int count = getMongoStore().updateEntities(MongoUserEntity.class, query, update, invocationContext);
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         DBObject query = new QueryBuilder()
                 .and("realmId").is(realm.getId())
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
index 1d7c279..382d01f 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -5,9 +5,11 @@ import org.infinispan.Version;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UserSessionProviderFactory;
 import org.keycloak.models.session.UserSessionPersisterProvider;
@@ -19,6 +21,9 @@ import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.initializer.InfinispanUserSessionInitializer;
 import org.keycloak.models.sessions.infinispan.initializer.OfflineUserSessionLoader;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.PostMigrationEvent;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
 
 /**
  * Uses Infinispan to store user sessions. On EAP 6.4 (Infinispan 5.2) map reduce is not supported for local caches as a work around
@@ -68,13 +73,20 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
         });
 
         // Max count of worker errors. Initialization will end with exception when this number is reached
-        int maxErrors = config.getInt("maxErrors", 20);
+        final int maxErrors = config.getInt("maxErrors", 20);
 
         // Count of sessions to be computed in each segment
-        int sessionsPerSegment = config.getInt("sessionsPerSegment", 100);
+        final int sessionsPerSegment = config.getInt("sessionsPerSegment", 100);
 
-        // TODO: Possibility to run this asynchronously to not block start time
-        loadPersistentSessions(factory, maxErrors, sessionsPerSegment);
+        factory.register(new ProviderEventListener() {
+
+            @Override
+            public void onEvent(ProviderEvent event) {
+                if (event instanceof PostMigrationEvent) {
+                    loadPersistentSessions(factory, maxErrors, sessionsPerSegment);
+                }
+            }
+        });
     }
 
 
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index 01ddcff..7fc77a8 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -8,9 +8,12 @@ import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.events.Details;
 import org.keycloak.events.EventType;
 import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.validation.Validation;
 
@@ -44,8 +47,11 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
         context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, context.getUser().getEmail()).success();
         LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
 
+        setupKey(context.getClientSession());
+
         LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
                 .setClientSessionCode(context.generateCode())
+                .setClientSession(context.getClientSession())
                 .setUser(context.getUser());
         Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
         context.challenge(challenge);
@@ -87,4 +93,9 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
     public String getId() {
         return UserModel.RequiredAction.VERIFY_EMAIL.name();
     }
+
+    public static void setupKey(ClientSessionModel clientSession) {
+        String secret = HmacOTP.generateSecret(10);
+        clientSession.setNote(Constants.VERIFY_EMAIL_KEY, secret);
+    }
 }
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 83b8d6e..a07999d 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -11,6 +11,7 @@ import org.keycloak.migration.MigrationModelManager;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.PostMigrationEvent;
 import org.keycloak.offlineconfig.AdminRecovery;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.DefaultKeycloakSessionFactory;
@@ -83,6 +84,8 @@ public class KeycloakApplication extends Application {
         setupDefaultRealm(context.getContextPath());
 
         migrateModel();
+        sessionFactory.publish(new PostMigrationEvent());
+
         new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath());
         importRealms(context);
 
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 46bc553..20de78b 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -23,6 +23,8 @@ package org.keycloak.services.resources;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.requiredactions.VerifyEmail;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationProcessor;
@@ -49,6 +51,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.FormMessage;
+import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.RestartLoginCookie;
@@ -533,7 +536,7 @@ public class LoginActionsService {
         event.event(EventType.VERIFY_EMAIL);
         if (key != null) {
             Checks checks = new Checks();
-            if (!checks.verifyCode(key, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+            if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
@@ -547,11 +550,21 @@ public class LoginActionsService {
             UserSessionModel userSession = clientSession.getUserSession();
             UserModel user = userSession.getUser();
             initEvent(clientSession);
+            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail());
+
+            String keyFromSession = clientSession.getNote(Constants.VERIFY_EMAIL_KEY);
+            clientSession.removeNote(Constants.VERIFY_EMAIL_KEY);
+            if (!key.equals(keyFromSession)) {
+                logger.error("Invalid key for email verification");
+                event.error(Errors.INVALID_USER_CREDENTIALS);
+                throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+            }
+
             user.setEmailVerified(true);
 
             user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
 
-            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
+            event.success();
 
             String actionCookieValue = getActionCookie();
             if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
@@ -576,8 +589,11 @@ public class LoginActionsService {
 
             createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
 
+            VerifyEmail.setupKey(clientSession);
+
             return session.getProvider(LoginFormsProvider.class)
                     .setClientSessionCode(accessCode.getCode())
+                    .setClientSession(clientSession)
                     .setUser(userSession.getUser())
                     .createResponse(RequiredAction.VERIFY_EMAIL);
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index a9c0c58..c7f075f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -26,7 +26,9 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.common.util.KeycloakUriBuilder;
 import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
 import org.keycloak.events.Event;
 import org.keycloak.events.EventType;
 import org.keycloak.models.RealmModel;
@@ -130,7 +132,7 @@ public class RequiredActionEmailVerificationTest {
 
         String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
 
-        Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1].split("\\.")[1]);
+        Assert.assertEquals(mailCodeId, verificationUrl.split("code=")[1].split("\\&")[0].split("\\.")[1]);
 
         driver.navigate().to(verificationUrl.trim());
 
@@ -223,7 +225,7 @@ public class RequiredActionEmailVerificationTest {
 
         String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
 
-        Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1].split("\\.")[1]);
+        Assert.assertEquals(mailCodeId, verificationUrl.split("code=")[1].split("\\&")[0].split("\\.")[1]);
 
         driver.manage().deleteAllCookies();
 
@@ -238,6 +240,42 @@ public class RequiredActionEmailVerificationTest {
 
         assertTrue(loginPage.isCurrent());
     }
+
+
+    @Test
+    public void verifyInvalidKeyOrCode() throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        Assert.assertTrue(verifyEmailPage.isCurrent());
+        String resendEmailLink = verifyEmailPage.getResendEmailLink();
+        String keyInsteadCodeURL = resendEmailLink.replace("code=", "key=");
+
+        AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
+        Event sendEvent = emailEvent.assertEvent();
+        String sessionId = sendEvent.getSessionId();
+        String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
+
+        driver.navigate().to(keyInsteadCodeURL);
+
+        events.expectRequiredAction(EventType.VERIFY_EMAIL_ERROR)
+                .error(Errors.INVALID_CODE)
+                .client((String)null)
+                .user((String)null)
+                .session((String)null)
+                .clearDetails()
+                .assertEvent();
+
+        String badKeyURL = KeycloakUriBuilder.fromUri(resendEmailLink).queryParam("key", "foo").build().toString();
+        driver.navigate().to(badKeyURL);
+
+        events.expectRequiredAction(EventType.VERIFY_EMAIL_ERROR)
+                .error(Errors.INVALID_USER_CREDENTIALS)
+                .session(sessionId)
+                .detail("email", "test-user@localhost")
+                .detail(Details.CODE_ID, mailCodeId)
+                .assertEvent();
+    }
     
     private String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
     	Multipart multipart = (Multipart) message.getContent();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index 258dd3c..6601855 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -5,6 +5,7 @@ import org.junit.Test;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.managers.ClientManager;
@@ -283,6 +284,36 @@ public class UserModelTest extends AbstractModelTest {
         Assert.assertNull(session.users().getUserByUsername("user1", realm));
     }
 
+    @Test
+    public void testGrantToAll() {
+        RealmModel realm1 = realmManager.createRealm("realm1");
+        RoleModel role1 = realm1.addRole("role1");
+        UserModel user1 = realmManager.getSession().users().addUser(realm1, "user1");
+        UserModel user2 = realmManager.getSession().users().addUser(realm1, "user2");
+
+        RealmModel realm2 = realmManager.createRealm("realm2");
+        UserModel realm2User1 = realmManager.getSession().users().addUser(realm2, "user1");
+
+        commit();
+
+        realm1 = realmManager.getRealmByName("realm1");
+        role1 = realm1.getRole("role1");
+        realmManager.getSession().users().grantToAllUsers(realm1, role1);
+
+        commit();
+
+        realm1 = realmManager.getRealmByName("realm1");
+        role1 = realm1.getRole("role1");
+        user1 = realmManager.getSession().users().getUserByUsername("user1", realm1);
+        user2 = realmManager.getSession().users().getUserByUsername("user2", realm1);
+        Assert.assertTrue(user1.hasRole(role1));
+        Assert.assertTrue(user2.hasRole(role1));
+
+        realm2 = realmManager.getRealmByName("realm2");
+        realm2User1 = realmManager.getSession().users().getUserByUsername("user1", realm2);
+        Assert.assertFalse(realm2User1.hasRole(role1));
+    }
+
     public static void assertEquals(UserModel expected, UserModel actual) {
         Assert.assertEquals(expected.getUsername(), actual.getUsername());
         Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
index cfcfbb4..9968ce1 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
@@ -50,4 +50,8 @@ public class VerifyEmailPage extends AbstractPage {
         resendEmailLink.click();
     }
 
+    public String getResendEmailLink() {
+        return resendEmailLink.getAttribute("href");
+    }
+
 }