8000 Add version column to credential table to avoid simultaneous recovery codes updates by rmartinc · Pull Request #38313 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add version column to credential table to avoid simultaneous recovery codes updates #38313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;
import jakarta.persistence.Version;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
Expand Down Expand Up @@ -73,6 +74,10 @@ public class CredentialEntity {
@Column(name="SALT")
protected byte[] salt;

@Version
@Column(name="VERSION")
private int version;

public String getId() {
return id;
}
Expand Down
11 changes: 11 additions & 0 deletions model/jpa/src/main/resources/META-INF/jpa-changelog-26.2.0.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,15 @@
</createTable>
</changeSet>

<changeSet author="keycloak" id="26.2.0-26106">
<addColumn tableName="CREDENTIAL">
<column name="VERSION" type="INT" defaultValueNumeric="0" />
</addColumn>
<modifySql dbms="mssql">
<!-- ensure that existing rows also get the new values on mssql -->
<!-- https://github.com/liquibase/liquibase/issues/4644 -->
<replace replace="DEFAULT 0" with="DEFAULT 0 WITH VALUES" />
</modifySql>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public void authenticate(AuthenticationFlowContext context) {

@Override
public void action(AuthenticationFlowContext context) {
context.getEvent().detail(Details.CREDENTIAL_TYPE, RecoveryAuthnCodesCredentialModel.TYPE);
context.getEvent().detail(Details.CREDENTIAL_TYPE, RecoveryAuthnCodesCredentialModel.TYPE)
.user(context.getUser());
if (isRecoveryAuthnCodeInputValid(context)) {
context.success();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.authentication;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;

/**
*
* @author rmartinc
*/
public class DelayedAuthenticator implements Authenticator {

@Override
public void authenticate(AuthenticationFlowContext context) {
final long time = context.getAuthenticatorConfig() != null
? Long.parseLong(context.getAuthenticatorConfig().getConfig().getOrDefault("delay", "1000"))
: 1000;
if (time > 0) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
context.success();
}

@Override
public void action(AuthenticationFlowContext context) {
// no-op
}

@Override
public boolean requiresUser() {
return false;
}

@Override
public boolean 8000 configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}

@Override
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
// no-op
}

@Override
public void close() {
// no-op
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.authentication;

import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.a 6D40 uthentication.AuthenticatorFactory;
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;

/**
* <p>Just an authenticator that delays the authenticator some millis.</p>
*
* @author rmartinc
*/
public class DelayedAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {

public static final String PROVIDER_ID = "delayed-authenticator";

@Override
public String getId() {
return PROVIDER_ID;
}

@Override
public Authenticator create(KeycloakSession session) {
return new DelayedAuthenticator();
}

@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[]{
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED
};
}

@Override
public boolean isUserSetupAllowed() {
return false;
}

@Override
public boolean isConfigurable() {
return true;
}

@Override
public String getHelpText() {
return "Just delay the autentication some millis.";
}

9E88 @Override
public String getDisplayType() {
return "TEST: Delayed authenticator";
}

@Override
public String getReferenceCategory() {
return "Delayed authenticator";
}

@Override
public void init(Config.Scope config) {
// no-op
}

@Override
public void postInit(KeycloakSessionFactory factory) {
// no-op
}

@Override
public void close() {
// no-op
}

@Override
public List<ProviderConfigProperty> getConfigProperties() {
return ProviderConfigurationBuilder.create()
.property()
.name("delay")
.label("Delay time in millis")
.helpText("The delayed time to apply in the authentication.")
.type(ProviderConfigProperty.INTEGER_TYPE)
.defaultValue("1000")
.add()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ org.keycloak.testsuite.forms.SetClientNoteAuthenticator
org.keycloak.testsuite.forms.PassThroughRegistration
org.keycloak.testsuite.forms.ClickThroughAuthenticator
org.keycloak.testsuite.forms.ErrorEventAuthenticator
org.keycloak.testsuite.authentication.DelayedAuthenticatorFactory
org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
Expand All @@ -29,4 +30,4 @@ org.keycloak.examples.providersoverride.CustomValidateUsername
org.keycloak.examples.providersoverride.CustomValidatePassword1
org.keycloak.examples.providersoverride.CustomValidatePassword2
org.keycloak.examples.providersoverride.CustomValidatePassword3
org.keycloak.examples.providersoverride.CustomValidateOTP
org.keycloak.examples.providersoverride.CustomValidateOTP
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.keycloak.testsuite.pages;

import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
Expand Down Expand Up @@ -34,7 +36,13 @@ public void enterRecoveryAuthnCode(String recoveryCode) {
}

public void clickSignInButton() {
signInButton.click();
UIUtils.clickLink(signInButton);
}

public void clickSignInButtonViaJavaScriptNoDelay() {
// submit the form via JS but with a setTimeout to avoid any delay
final JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.setTimeout(function() {document.forms[0].submit()}, 0)");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ public EventRepresentation assertEvent(boolean ignorePreviousEvents, int seconds
Assert.fail("Did not find the event of expected type " + expected.getType() +". Events present: " + presentedEventTypes);
return null; // Unreachable code
9150 } else {
return assertEvent(poll());
return assertEvent(poll(seconds));
}
}

Expand Down Expand Up @@ -453,6 +453,11 @@ public EventRepresentation assertEvent(EventRepresentation actual) {

return actual;
}

@Override
public String toString() {
return this.getClass().getSimpleName() + ":" + expected.getType();
}
}

public static Matcher<String> isCodeId() {
Expand Down
Loading
Loading
0