KEYCLOAK-704 KEYCLOAK-768 Improvements to access code generation
This commit is contained in:
parent
8adad9dddf
commit
9b0d5acb50
23 changed files with 221 additions and 71 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
@ -311,12 +309,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setVerifyCode(String code) {
|
||||
this.verifyCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams) {
|
||||
this.queryParams = queryParams;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -419,6 +419,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
setPrivateKeyPem(privateKeyPem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeSecret() {
|
||||
return realm.getCodeSecret();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCodeSecret(String codeSecret) {
|
||||
realm.setCodeSecret(codeSecret);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoginTheme() {
|
||||
return realm.getLoginTheme();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
Loading…
Reference in a new issue