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 b50a4f465f..5b0a0da893 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 @@
-
+
@@ -47,14 +47,14 @@
-
+
-
-
+
+
\ 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 0000000000..4e0129eee7
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.1.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 6cd96c626c..3010118039 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 @@
-
+
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 60b063513e..401cf74b2d 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/distribution/saml-adapters/jetty91-adapter-zip/pom.xml b/distribution/saml-adapters/jetty91-adapter-zip/pom.xml
index 2c15be444a..733aabf157 100755
--- a/distribution/saml-adapters/jetty91-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/jetty91-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
keycloak-parent
org.keycloak
- 1.6.0.Final-SNAPSHOT
+
../../../pom.xml
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 33d62dca2d..2036afbd8a 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'
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)
loginHint - used to pre-fill the username/email field on the login form
action - if value is 'register' then user is redirected to registration page, otherwise to login page
+ locale - specifies the desired locale for the UI
diff --git a/examples/pom.xml b/examples/pom.xml
index ec25be9438..6423a7c018 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -14,14 +14,18 @@
pom
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+
-
- org.apache.maven.plugins
- maven-deploy-plugin
-
- true
-
-
org.apache.maven.plugins
maven-war-plugin
diff --git a/examples/saml/post-with-encryption/pom.xml b/examples/saml/post-with-encryption/pom.xml
index 0c95d95563..090f6afc0e 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">
4.0.0
- org.keycloak.examples
+
+ keycloak-examples-saml-parent
+ org.keycloak
+ 1.7.0.Final-SNAPSHOT
+
+
saml-post-encryption
- 1.6.0.Final-SNAPSHOT
war
diff --git a/examples/saml/post-with-signature/pom.xml b/examples/saml/post-with-signature/pom.xml
index 8841ac054c..14db058a9b 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">
4.0.0
- org.keycloak.examples
+
+ keycloak-examples-saml-parent
+ org.keycloak
+ 1.7.0.Final-SNAPSHOT
+
+
saml-post-signatures
- 1.6.0.Final-SNAPSHOT
war
diff --git a/examples/saml/redirect-with-signature/pom.xml b/examples/saml/redirect-with-signature/pom.xml
index 55f426b7df..06a4e55d5e 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">
4.0.0
- org.keycloak.examples
+
+ keycloak-examples-saml-parent
+ org.keycloak
+ 1.7.0.Final-SNAPSHOT
+
+
saml-redirect-signatures
- 1.6.0.Final-SNAPSHOT
war
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 e19a266b9d..404de17ea2 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 @@
-
+
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 e472fff1ff..3f46b76ba1 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 @@
-
+
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 cb5f2be8cd..fccfce160c 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 realmRolesRequested, MultivaluedMap resourceRolesRequested, List 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 7fc1bcddcb..6125d0b83b 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());
@@ -531,6 +533,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
return this;
}
+ @Override
+ public LoginFormsProvider setClientSession(ClientSessionModel clientSession) {
+ this.clientSession = clientSession;
+ return this;
+ }
+
@Override
public LoginFormsProvider setAccessRequest(List realmRolesRequested, MultivaluedMap resourceRolesRequested, List protocolMappersRequested) {
this.realmRolesRequested = realmRolesRequested;
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 f10d031d07..a29271895b 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 Bill Burke
@@ -32,4 +33,11 @@ public class ClientBean {
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
}
+ public Map 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 d189a361f8..7b378de347 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 88817acb27..45c73a31cf 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 43bdc7dc68..8977def5c3 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 bea51e0473..cde5eb9411 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -333,6 +333,12 @@ public class UserFederationManager implements UserProvider {
return session.userStorage().getFederatedIdentity(user, socialProvider, realm);
}
+ @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()) {
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 7d7064d76e..82be2fe160 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 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 0000000000..513836f3ba
--- /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 Marek Posolda
+ */
+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 8edfe3ec2e..8c24262a12 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
@@ -422,6 +422,13 @@ public class FileUserProvider implements UserProvider {
return this.addUser(realm, KeycloakModelUtils.generateId(), username.toLowerCase(), true, true);
}
+ @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 69fc5cf577..9045f911f0 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
@@ -303,6 +303,12 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
return getDelegate().validCredentials(realm, input);
}
+ @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());
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 d4d533a9dc..c9031476cf 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/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 1ce81e8297..45dfc3de95 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/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 358e6f2d28..9fc9735559 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
@@ -368,6 +368,19 @@ public class MongoUserProvider implements UserProvider {
return this.addUser(realm, null, username, true, true);
}
+ @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()
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 1d7c279542..382d01f630 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 01ddcffa7e..7fc77a81d8 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 83b8d6e68e..a07999d6c1 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 46bc55393e..20de78b7d8 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 a9c0c58373..c7f075f4d6 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 258dd3cbd7..66018552c5 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 cfcfbb4fdb..9968ce1087 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");
+ }
+
}