keycloak-memoizeit
Changes
connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java 79(+79 -0)
connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java 26(+26 -0)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java 10(+1 -9)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 6(+6 -0)
Details
diff --git a/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java
new file mode 100644
index 0000000..76a02a7
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AddRealmCodeSecret.java
@@ -0,0 +1,79 @@
+package org.keycloak.connections.jpa.updater.liquibase.custom;
+
+import liquibase.change.custom.CustomSqlChange;
+import liquibase.database.Database;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.CustomChangeException;
+import liquibase.exception.SetupException;
+import liquibase.exception.ValidationErrors;
+import liquibase.resource.ResourceAccessor;
+import liquibase.statement.SqlStatement;
+import liquibase.statement.core.UpdateStatement;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AddRealmCodeSecret implements CustomSqlChange {
+
+ private String confirmationMessage;
+
+ @Override
+ public SqlStatement[] generateStatements(Database database) throws CustomChangeException {
+ try {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Generated codeSecret for realms: ");
+
+ Connection connection = ((JdbcConnection) (database.getConnection())).getWrappedConnection();
+ ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM REALM WHERE CODE_SECRET IS NULL");
+
+ ArrayList<SqlStatement> statements = new ArrayList<SqlStatement>();
+ while (resultSet.next()) {
+ String id = resultSet.getString(1);
+
+ UpdateStatement statement = new UpdateStatement(null, null, "REALM")
+ .addNewColumnValue("CODE_SECRET", KeycloakModelUtils.generateCodeSecret())
+ .setWhereClause("ID='" + id + "'");
+ statements.add(statement);
+
+ if (!resultSet.isFirst()) {
+ sb.append(", ");
+ }
+ sb.append(id);
+ }
+
+ if (!statements.isEmpty()) {
+ confirmationMessage = sb.toString();
+ }
+
+ return statements.toArray(new SqlStatement[statements.size()]);
+ } catch (Exception e) {
+ throw new CustomChangeException("Failed to add realm code secret", e);
+ }
+ }
+
+ @Override
+ public String getConfirmationMessage() {
+ return confirmationMessage;
+ }
+
+ @Override
+ public void setUp() throws SetupException {
+
+ }
+
+ @Override
+ public void setFileOpener(ResourceAccessor resourceAccessor) {
+
+ }
+
+ @Override
+ public ValidationErrors validate(Database database) {
+ return null;
+ }
+
+}
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
index 8344266..c94b206 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
@@ -43,9 +43,10 @@
</addColumn>
<addColumn tableName="REALM">
<column name="CERTIFICATE" type="VARCHAR(2048)"/>
+ <column name="CODE_SECRET" type="VARCHAR(255)"/>
</addColumn>
<addColumn tableName="CLIENT">
- <column name="NODE_REREG_TIMEOUT" type="INT"/>
+ <column name="NODE_REREG_TIMEOUT" type="INT" defaultValue="0"/>
</addColumn>
<addPrimaryKey columnNames="CLIENT_ID, NAME" constraintName="CONSTRAINT_3C" tableName="CLIENT_ATTRIBUTES"/>
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_5E" tableName="CLIENT_SESSION_NOTE"/>
@@ -53,5 +54,6 @@
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_ATTRIBUTES" constraintName="FK3C47C64BEACCA966" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_NOTE" constraintName="FK5EDFB00FF51C2736" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="APP_NODE_REGISTRATIONS" constraintName="FK8454723BA992F594" referencedColumnNames="ID" referencedTableName="CLIENT"/>
+ <customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.AddRealmCodeSecret"/>
</changeSet>
</databaseChangeLog>
\ No newline at end of file
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java
index 89d372f..f080244 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java
@@ -1,5 +1,13 @@
package org.keycloak.connections.mongo.updater.updates;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.Arrays;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -14,6 +22,24 @@ public class Update1_1_0_Beta1 extends Update {
public void update() {
deleteEntries("clientSessions");
deleteEntries("sessions");
+
+ addRealmCodeSecret();
+ }
+
+ private void addRealmCodeSecret() {
+ DBCollection realms = db.getCollection("realms");
+
+ DBObject query = new QueryBuilder()
+ .and("codeSecret").is(null).get();
+
+ DBCursor objects = realms.find(query);
+ while (objects.hasNext()) {
+ DBObject object = objects.next();
+ object.put("codeSecret", KeycloakModelUtils.generateCodeSecret());
+ realms.save(object);
+
+ log.debugv("Added realm.codeSecret, id={0}", object.get("id"));
+ }
}
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 3346371..b0a0a21 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -44,6 +44,7 @@ public class RealmRepresentation {
protected String privateKey;
protected String publicKey;
protected String certificate;
+ protected String codeSecret;
protected RolesRepresentation roles;
protected List<String> defaultRoles;
protected Set<String> requiredCredentials;
@@ -229,6 +230,14 @@ public class RealmRepresentation {
this.certificate = certificate;
}
+ public String getCodeSecret() {
+ return codeSecret;
+ }
+
+ public void setCodeSecret(String codeSecret) {
+ this.codeSecret = codeSecret;
+ }
+
public Boolean isPasswordCredentialGrantAllowed() {
return passwordCredentialGrantAllowed;
}
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 3e1eb51..4fd2de9 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
@@ -50,8 +50,6 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setClient(ClientModel client);
- LoginFormsProvider setVerifyCode(String code);
-
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams);
public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData);
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 9a76e78..744d588 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
@@ -51,7 +51,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
- private String verifyCode;
private String message;
private String accessCode;
private Response.Status status = Response.Status.OK;
@@ -110,8 +109,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
case VERIFY_EMAIL:
try {
UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
- builder.queryParam("code", accessCode);
- builder.queryParam("key", verifyCode);
+ builder.queryParam("key", accessCode);
String link = builder.build(realm.getName()).toString();
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@@ -312,12 +310,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
@Override
- public LoginFormsProvider setVerifyCode(String code) {
- this.verifyCode = code;
- return this;
- }
-
- @Override
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams) {
this.queryParams = queryParams;
return this;
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 4371e26..ef38009 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -41,6 +41,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
+ private String codeSecret;
private String loginTheme;
private String accountTheme;
@@ -271,6 +272,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.privateKeyPem = privateKeyPem;
}
+ public String getCodeSecret() {
+ return codeSecret;
+ }
+
+ public void setCodeSecret(String codeSecret) {
+ this.codeSecret = codeSecret;
+ }
+
public String getLoginTheme() {
return loginTheme;
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d1895ef..4601fcc 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -97,6 +97,10 @@ public interface RealmModel extends RoleContainerModel {
void setPublicKey(PublicKey publicKey);
+ String getCodeSecret();
+
+ void setCodeSecret(String codeSecret);
+
X509Certificate getCertificate();
void setCertificate(X509Certificate certificate);
String getCertificatePem();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index f5a1b3a..09111b4 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -119,6 +119,8 @@ public final class KeycloakModelUtils {
throw new RuntimeException(e);
}
realm.setCertificate(certificate);
+
+ realm.setCodeSecret(generateCodeSecret());
}
public static void generateRealmCertificate(RealmModel realm) {
@@ -161,6 +163,10 @@ public final class KeycloakModelUtils {
return secret;
}
+ public static String generateCodeSecret() {
+ return UUID.randomUUID().toString();
+ }
+
public static ApplicationModel createApplication(RealmModel realm, String name) {
ApplicationModel app = realm.addApplication(name);
generateSecret(app);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 85331ac..7d431c3 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -91,6 +91,7 @@ public class ModelToRepresentation {
KeycloakModelUtils.generateRealmCertificate(realm);
}
rep.setCertificate(realm.getCertificatePem());
+ rep.setCodeSecret(realm.getCodeSecret());
rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
rep.setRegistrationAllowed(realm.isRegistrationAllowed());
rep.setRememberMe(realm.isRememberMe());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 7c6e29f..5ba8d06 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -92,6 +92,12 @@ public class RepresentationToModel {
} else {
newRealm.setCertificatePem(rep.getCertificate());
}
+ if (rep.getCodeSecret() == null) {
+ newRealm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
+ } else {
+ newRealm.setCodeSecret(rep.getCodeSecret());
+ }
+
if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme());
if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index d96a9ea..76c9204 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -57,6 +57,7 @@ public class CachedRealm {
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
+ private String codeSecret;
private String loginTheme;
private String accountTheme;
@@ -115,6 +116,7 @@ public class CachedRealm {
publicKeyPem = model.getPublicKeyPem();
privateKeyPem = model.getPrivateKeyPem();
certificatePem = model.getCertificatePem();
+ codeSecret = model.getCodeSecret();
loginTheme = model.getLoginTheme();
accountTheme = model.getAccountTheme();
@@ -267,6 +269,10 @@ public class CachedRealm {
return privateKeyPem;
}
+ public String getCodeSecret() {
+ return codeSecret;
+ }
+
public List<RequiredCredentialModel> getRequiredCredentials() {
return requiredCredentials;
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index d471b4a..eaf9fca 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -374,7 +374,16 @@ public class RealmAdapter implements RealmModel {
setPrivateKeyPem(privateKeyPem);
}
+ @Override
+ public String getCodeSecret() {
+ return updated != null ? updated.getCodeSecret() : cached.getCodeSecret();
+ }
+ @Override
+ public void setCodeSecret(String codeSecret) {
+ getDelegateForUpdate();
+ updated.setCodeSecret(codeSecret);
+ }
@Override
public List<RequiredCredentialModel> getRequiredCredentials() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index bcb5ad9..c78e916 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -82,6 +82,8 @@ public class RealmEntity {
protected String privateKeyPem;
@Column(name="CERTIFICATE", length = 2048)
protected String certificatePem;
+ @Column(name="CODE_SECRET", length = 255)
+ protected String codeSecret;
@Column(name="LOGIN_THEME")
protected String loginTheme;
@@ -284,6 +286,14 @@ public class RealmEntity {
this.privateKeyPem = privateKeyPem;
}
+ public String getCodeSecret() {
+ return codeSecret;
+ }
+
+ public void setCodeSecret(String codeSecret) {
+ this.codeSecret = codeSecret;
+ }
+
public Collection<RequiredCredentialEntity> getRequiredCredentials() {
return requiredCredentials;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 5578b88..ee97080 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -434,6 +434,16 @@ public class RealmAdapter implements RealmModel {
setPrivateKeyPem(privateKeyPem);
}
+ @Override
+ public String getCodeSecret() {
+ return realm.getCodeSecret();
+ }
+
+ @Override
+ public void setCodeSecret(String codeSecret) {
+ realm.setCodeSecret(codeSecret);
+ }
+
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index ba51c13..5963ab0 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -420,6 +420,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public String getCodeSecret() {
+ return realm.getCodeSecret();
+ }
+
+ @Override
+ public void setCodeSecret(String codeSecret) {
+ realm.setCodeSecret(codeSecret);
+ updateRealm();
+ }
+
+ @Override
public String getLoginTheme() {
return realm.getLoginTheme();
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 1c8c317..5bcbb87 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -16,6 +16,7 @@ import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OpenIDConnectService;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
@@ -201,6 +202,7 @@ public class SamlService {
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
clientSession.setRedirectUri(redirect);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+ clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
clientSession.setNote(SamlProtocol.SAML_BINDING, getBindingType());
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
clientSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index 96c3ef6..0c60ccb 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -550,7 +550,7 @@ public class OpenIDConnectService {
String[] parts = code.split("\\.");
if (parts.length == 2) {
try {
- event.detail(Details.CODE_ID, new String(Base64Url.decode(parts[1])));
+ event.detail(Details.CODE_ID, new String(parts[1]));
} catch (Throwable t) {
}
}
@@ -776,6 +776,7 @@ public class OpenIDConnectService {
clientSession.setAuthMethod(OpenIDConnect.LOGIN_PROTOCOL);
clientSession.setRedirectUri(redirect);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+ clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
clientSession.setNote(OpenIDConnect.STATE_PARAM, state);
if (scopeParam != null) clientSession.setNote(OpenIDConnect.SCOPE_PARAM, scopeParam);
if (responseType != null) clientSession.setNote(OpenIDConnect.RESPONSE_TYPE_PARAM, responseType);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 9dbfbea..4856c02 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -290,9 +290,6 @@ public class AuthenticationManager {
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
- String key = UUID.randomUUID().toString();
- clientSession.setNote("key", key);
- loginFormsProvider.setVerifyCode(key);
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index 46bba42..526e791 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -1,8 +1,5 @@
package org.keycloak.services.managers;
-import org.keycloak.OAuthErrorException;
-import org.keycloak.jose.jws.Algorithm;
-import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -11,11 +8,10 @@ import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.util.Base64Url;
import org.keycloak.util.Time;
-import java.nio.ByteBuffer;
import java.security.MessageDigest;
-import java.security.Signature;
import java.util.HashSet;
import java.util.Set;
+import java.util.UUID;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -23,6 +19,10 @@ import java.util.Set;
*/
public class ClientSessionCode {
+ public static final String ACTION_KEY = "action_key";
+
+ private static final byte[] HASH_SEPERATOR = "//".getBytes();
+
private final RealmModel realm;
private final ClientSessionModel clientSession;
@@ -34,14 +34,14 @@ public class ClientSessionCode {
public static ClientSessionCode parse(String code, KeycloakSession session) {
try {
String[] parts = code.split("\\.");
- String id = new String(Base64Url.decode(parts[1]));
+ String id = parts[1];
ClientSessionModel clientSession = session.sessions().getClientSession(id);
if (clientSession == null) {
return null;
}
- String hash = createSignatureHash(clientSession.getRealm(), clientSession);
+ String hash = createHash(clientSession.getRealm(), clientSession);
if (!hash.equals(parts[0])) {
return null;
}
@@ -56,14 +56,14 @@ public class ClientSessionCode {
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
try {
String[] parts = code.split("\\.");
- String id = new String(Base64Url.decode(parts[1]));
+ String id = parts[1];
ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
if (clientSession == null) {
return null;
}
- String hash = createSignatureHash(realm, clientSession);
+ String hash = createHash(realm, clientSession);
if (!hash.equals(parts[0])) {
return null;
}
@@ -111,6 +111,7 @@ public class ClientSessionCode {
public void setAction(ClientSessionModel.Action action) {
clientSession.setAction(action);
+ clientSession.setNote(ACTION_KEY, UUID.randomUUID().toString());
clientSession.setTimestamp(Time.currentTime());
}
@@ -138,29 +139,24 @@ public class ClientSessionCode {
}
private static String generateCode(RealmModel realm, ClientSessionModel clientSession) {
- String hash = createSignatureHash(realm, clientSession);
+ String hash = createHash(realm, clientSession);
StringBuilder sb = new StringBuilder();
sb.append(hash);
sb.append(".");
- sb.append(Base64Url.encode(clientSession.getId().getBytes()));
+ sb.append(clientSession.getId());
return sb.toString();
}
- private static String createSignatureHash(RealmModel realm, ClientSessionModel clientSession) {
+ private static String createHash(RealmModel realm, ClientSessionModel clientSession) {
try {
- Signature signature = Signature.getInstance(RSAProvider.getJavaAlgorithm(Algorithm.RS256));
- signature.initSign(realm.getPrivateKey());
- signature.update(clientSession.getId().getBytes());
- signature.update(ByteBuffer.allocate(4).putInt(clientSession.getTimestamp()));
- if (clientSession.getAction() != null) {
- signature.update(clientSession.getAction().toString().getBytes());
- }
- byte[] sign = signature.sign();
-
- MessageDigest digest = MessageDigest.getInstance("sha-1");
- digest.update(sign);
+ MessageDigest digest = MessageDigest.getInstance("sha-256");
+ digest.update(realm.getCodeSecret().getBytes());
+ digest.update(HASH_SEPERATOR);
+ digest.update(clientSession.getId().getBytes());
+ digest.update(HASH_SEPERATOR);
+ digest.update(clientSession.getNote(ACTION_KEY).getBytes());
return Base64Url.encode(digest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 5eff07b..2d91533 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -709,9 +709,6 @@ public class UsersResource {
try {
UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
builder.queryParam("code", accessCode.getCode());
- String key = UUID.randomUUID().toString();
- clientSession.setNote("key", key);
- builder.queryParam("key", key);
String link = builder.build(realm.getName()).toString();
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
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 7310d74..56bbd93 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -714,22 +714,17 @@ public class LoginActionsService {
@Path("email-verification")
@GET
- public Response emailVerification(@QueryParam("code") String code) {
+ public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
event.event(EventType.VERIFY_EMAIL);
- if (uriInfo.getQueryParameters().containsKey("key")) {
+ if (key != null) {
Checks checks = new Checks();
- if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
+ if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
ClientSessionModel clientSession = accessCode.getClientSession();
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
- String key = uriInfo.getQueryParameters().getFirst("key");
- String keyNote = clientSession.getNote("key");
- if (key == null || !key.equals(keyNote)) {
- return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your email.");
- }
initEvent(clientSession);
user.setEmailVerified(true);
@@ -745,16 +740,11 @@ public class LoginActionsService {
}
ClientSessionCode accessCode = checks.clientCode;
ClientSessionModel clientSession = accessCode.getClientSession();
- String verifyCode = UUID.randomUUID().toString();
- clientSession.setNote("key", verifyCode);
UserSessionModel userSession = clientSession.getUserSession();
- UserModel user = userSession.getUser();
-
initEvent(clientSession);
return Flows.forms(session, realm, null, uriInfo)
.setClientSessionCode(accessCode.getCode())
- .setVerifyCode(verifyCode)
.setUser(userSession.getUser())
.createResponse(RequiredAction.VERIFY_EMAIL);
}
@@ -762,22 +752,14 @@ public class LoginActionsService {
@Path("password-reset")
@GET
- public Response passwordReset(@QueryParam("code") String code) {
+ public Response passwordReset(@QueryParam("code") String code, @QueryParam("key") String key) {
event.event(EventType.SEND_RESET_PASSWORD);
- if (uriInfo.getQueryParameters().containsKey("key")) {
+ if (key != null) {
Checks checks = new Checks();
- if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD)) {
+ if (!checks.check(key, ClientSessionModel.Action.UPDATE_PASSWORD)) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
- ClientSessionModel clientSession = accessCode.getClientSession();
- UserSessionModel userSession = clientSession.getUserSession();
- UserModel user = userSession.getUser();
- String key = uriInfo.getQueryParameters().getFirst("key");
- String keyNote = clientSession.getNote("key");
- if (key == null || !key.equals(keyNote)) {
- return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
- }
return Flows.forms(session, realm, null, uriInfo)
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
@@ -842,10 +824,7 @@ public class LoginActionsService {
try {
UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
- builder.queryParam("code", accessCode.getCode());
- String verifyCode = UUID.randomUUID().toString();
- clientSession.setNote("key", verifyCode);
- builder.queryParam("key", verifyCode);
+ builder.queryParam("key", accessCode.getCode());
String link = builder.build(realm.getName()).toString();
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
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 f2b8b26..0f2b13f 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
@@ -122,7 +122,7 @@ public class RequiredActionEmailVerificationTest {
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
- //Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1]);
+ Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1].split("\\.")[1]);
driver.navigate().to(verificationUrl.trim());