8000 Migrate Standard token exchange tests by graziang · Pull Request #47516 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Migrate Standard token exchange tests - #47516

Merged
mposolda merged 3 commits into
keycloak:mainfrom
graziang:issue-47491
May 27, 2026
Merged

mposolda merged 3 commits into
keycloak:mainfrom
graziang:issue-47491

Conversation

@graziang
@graziang graziang commented Mar 27, 2026 •
Copy link
Copy Markdown
Contributor

Migrate StandardTokenExchangeV2Test to the new test suite.
Tests divided in 3 test classes:

  • StandardTokenExchangeV2Test
  • StandardTokenExchangeClientPoliciesV2Test
  • StandardTokenExchangeRefreshTokenV2Test

I didn’t remove StandardTokenExchangeV2Test from old test suite because it’s used to run V2 tests with the V1 feature enabled, and I didn’t want to add V1 tests to the new test suite. It’s probably better to remove it later with follow-up tasks on token exchange.

@mposolda mposolda left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graziang Thanks for the changes and for migrating the test!

Few points:

  • Are new token-exchange executed with GH actions during new testsuite run? I did not check, just asking :-)

  • IMO it is OK to keep the StandardTokenExchangeV2Test also in the new testsuite as you pointed, but maybe it would be good to at least make sure that it is not executed within GH actions in the old testsuite? As otherwise, we would have same token-exchange test scenarios tested multiple times. Maybe it would be OK to update StandardTokenExchangeV2Test in the old testsuite to the abstract class. AFAIK this would mean that it will not be executed (would be good to doublecheck in GH actions). This may include some javadoc comment specifying on why it is abstract and why the tests are not executed. Also it can be good to deprecate that class in the old testsuite and mention in the javadoc that new tests should not be put to this class, but rather to the classes in the new testsuite. Finally, I wonder whether to remove some (or most) of tests from that class and keep just some "smoke" tests - especially since token-exchange V1 is deprecated now. But if you prefer to rather not do that in this PR, it is OK IMO.

  • I will ask also "testsuite wg" team to review the PR changes in the test-framework

@lhanusov lhanusov left a comment •
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graziang @mposolda Hi Giuseppe, thank you for this PR, it's a huge change, it took me a while to go through it. Don't be scared for multiple request for change, I would like to understand if these changes are actually required and not to mess the framework itself. Thanks :-)

Comment on lines +57 to +68
public void update(ManagedClient.ClientUpdate... updates) {
ClientRepresentation rep = admin().toRepresentation();

ClientConfigBuilder configBuilder = ClientConfigBuilder.update(rep);
for (ManagedClient.ClientUpdate update : updates) {
configBuilder = update.update(configBuilder);
}

ClientRepresentation updated = configBuilder.build();
admin().update(updated);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this changes is not required, please use updateWithCleanup

Suggested change
public void update(ManagedClient.ClientUpdate... updates) {
ClientRepresentation rep = admin().toRepresentation();
ClientConfigBuilder configBuilder = ClientConfigBuilder.update(rep);
for (ManagedClient.ClientUpdate update : updates) {
configBuilder = update.update(configBuilder);
}
ClientRepresentation updated = configBuilder.build();
admin().update(updated);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

Comment on lines +105 to +131
UserRepresentation john;

UserRepresentation mike;

UserRepresentation alice;


@BeforeEach
public void setup() {
john = AdminApiUtil.findUserByUsernameId(realm.admin(), "john").toRepresentation();
mike = AdminApiUtil.findUserByUsernameId(realm.admin(), "mike").toRepresentation();
alice = AdminApiUtil.findUserByUsernameId(realm.admin(), "alice").toRepresentation();

// Assign roles to service account user
UserRepresentation serviceAccount = subjectClient.admin().getServiceAccountUser();

// Assign realm role default-roles-test
RoleRepresentation defaultRolesTest = realm.admin().roles().get("default-roles-test").toRepresentation();
realm.admin().users().get(serviceAccount.getId()).roles().realmLevel().add(List.of(defaultRolesTest));

// Assign client role target-client1-role
String targetClient1Id = realm.admin().clients().findByClientId("target-client1").get(0).getId();
RoleRepresentation targetClient1Role = realm.admin().clients().get(targetClient1Id).roles().get("target-client1-role").toRepresentation();
realm.admin().users().get(serviceAccount.getId()).roles().clientLevel(targetClient1Id).add(List.of(targetClient1Role));
enableEvents(realm);
}

@lhanusov lhanusov Apr 1, 2026 •
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, remove these changes and use particular ManagedObjects. I suggest to have UserSupplier with attachTo option as ClientSupplier has already, but it needs to be implemented first, feel free to do it.
Please check: https://github.com/keycloak/keycloak/blob/main/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientSupplier.java#L31

Suggested change
UserRepresentation john;
UserRepresentation mike;
UserRepresentation alice;
@BeforeEach
public void setup() {
john = AdminApiUtil.findUserByUsernameId(realm.admin(), "john").toRepresentation();
mike = AdminApiUtil.findUserByUsernameId(realm.admin(), "mike").toRepresentation();
alice = AdminApiUtil.findUserByUsernameId(realm.admin(), "alice").toRepresentation();
// Assign roles to service account user
UserRepresentation serviceAccount = subjectClient.admin().getServiceAccountUser();
// Assign realm role default-roles-test
RoleRepresentation defaultRolesTest = realm.admin().roles().get("default-roles-test").toRepresentation();
realm.admin().users().get(serviceAccount.getId()).roles().realmLevel().add(List.of(defaultRolesTest));
// Assign client role target-client1-role
String targetClient1Id = realm.admin().clients().findByClientId("target-client1").get(0).getId();
RoleRepresentation targetClient1Role = realm.admin().clients().get(targetClient1Id).roles().get("target-client1-role").toRepresentation();
realm.admin().users().get(serviceAccount.getId()).roles().clientLevel(targetClient1Id).add(List.of(targetClient1Role));
enableEvents(realm);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done and create added attachTo to UserSupplier

import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class BaseAbstractTokenExchangeTest {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class BaseAbstractTokenExchangeTest {
public class AbstractBaseTokenExchangeTest {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@InjectPage
ConsentPage consentPage;

@InjectRealm(config = TokenExchangeRealm.class)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this realm field, right after the class name, it must be first in a row, hence it will create the realm two times, once calling InjectEvents and second when recreating InjectRealm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

8BD0 The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +132 to +141
private void enableEvents(ManagedRealm realm) {
RealmEventsConfigRepresentation realmEventsConfig = realm.admin().getRealmEventsConfig();
List<String> enabledEventTypes = realmEventsConfig.getEnabledEventTypes();
if (!enabledEventTypes.contains(EventType.REFRESH_TOKEN.name())) {
enabledEventTypes.add(EventType.REFRESH_TOKEN.name());
enabledEventTypes.add(EventType.INTROSPECT_TOKEN.name());
realm.admin().updateRealmEventsConfig(realmEventsConfig);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move it into the RealmConfig implementation directly and use realm.updateWithCleanup(...) or just realm.cleanup().add(...) if you need any change in a method

Suggested change
private void enableEvents(ManagedRealm realm) {
RealmEventsConfigRepresentation realmEventsConfig = realm.admin().getRealmEventsConfig();
List<String> enabledEventTypes = realmEventsConfig.getEnabledEventTypes();
if (!enabledEventTypes.contains(EventType.REFRESH_TOKEN.name())) {
enabledEventTypes.add(EventType.REFRESH_TOKEN.name());
enabledEventTypes.add(EventType.INTROSPECT_TOKEN.name());
realm.admin().updateRealmEventsConfig(realmEventsConfig);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is because the realm is created with all the default events if enabledEvents is null, and I can't add specific events in addition to default ones during creation, so I need to first create realm with default events and then set additional events after the creation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thanks for explanation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case maybe TestSetup annotation would be suitable to execute it:
https://github.com/keycloak/keycloak/blob/main/test-framework/docs/WRITING_TESTS.md#setup-and-cleanup

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to @testsetup, setup method only set the events now

private static final String POLICY_NAME = "MyPolicy";

@Test
void testClientPolicies() throws Exception {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void testClientPolicies() throws Exception {
public void testClientPolicies() throws Exception {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@Test
void testClientPolicies() throws Exception {
// Create executor configuration
TestRaiseExceptionExecutor.Configuration executorConfig = new TestRaiseExceptionExecutor.Configuration();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graziang @mposolda Can this custom-provider be replaced by a runOnServer or fetchOnServer stuff? if yes, please remove this provider addition.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exception handling: https://docs.junit.org/6.0.3/writing-tests/exception-handling.html

assertThrows(() -> {...});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to RejectRequestExecutor already present in Keycloak, so removed the custom executors an conditions from the framework

import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.condition.AbstractClientPolicyConditionProvider;

public class TestRaiseExceptionCondition extends AbstractClientPolicyConditionProvider<TestRaiseExceptionCondition.Configuration> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this provider has no usage, please remove it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory;

public class TestRaiseExceptionConditionFactory implements ClientPolicyConditionProviderFactory {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this factory provider has no usage, please remove it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@@ -0,0 +1 @@
org.keycloak.tests.providers.client.policies.condition.TestRaiseExceptionConditionFactory
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this service has no usage, please remove it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +465 to +480
/* protected void createClientScopeForRole(RealmResource realm, String clientId, String clientRoleName, String clientScopeName) {
final ClientResource client = ApiUtil.findClientByClientId(realm, clientId);
createClientScopeForRole(realm, client, clientRoleName, clientScopeName);
}

protected void createClientScopeForRole(RealmResource realm, ClientResource client, String clientRoleName, String clientScopeName) {
final String clientUUID = client.toRepresentation().getId();
final RoleRepresentation clientRole = ApiUtil.findClientRoleByName(client, clientRoleName).toRepresentation();

final ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
clientScope.setName(clientScopeName);
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
final String clientScopeId = ApiUtil.getCreatedId(realm.clientScopes().create(clientScope));
getCleanup().addClientScopeId(clientScopeId);
realm.clientScopes().get(clientScopeId).getScopeMappings().clientLevel(clientUUID).add(List.of(clientRole));
}*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, remove unused code

Suggested change
/* protected void createClientScopeForRole(RealmResource realm, String clientId, String clientRoleName, String clientScopeName) {
final ClientResource client = ApiUtil.findClientByClientId(realm, clientId);
createClientScopeForRole(realm, client, clientRoleName, clientScopeName);
}
protected void createClientScopeForRole(RealmResource realm, ClientResource client, String clientRoleName, String clientScopeName) {
final String clientUUID = client.toRepresentation().getId();
final RoleRepresentation clientRole = ApiUtil.findClientRoleByName(client, clientRoleName).toRepresentation();
final ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
clientScope.setName(clientScopeName);
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
final String clientScopeId = ApiUtil.getCreatedId(realm.clientScopes().create(clientScope));
getCleanup().addClientScopeId(clientScopeId);
realm.clientScopes().get(clientScopeId).getScopeMappings().clientLevel(clientUUID).add(List.of(clientRole));
}*/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@graziang
graziang commented Apr 2, 2026
Copy link
Copy Markdown
Contributor Author

Thanks for the review!

@lhanusov I applied the suggestions, I also replied to the inline comments.

@mposolda I deprectated the old class, and removed almost all the tests, I checked that the tests are executed in the new test suite and not in the old one.

I added separate commits for easier review, let me know if I missed anything, Thanks!

@lhanusov lhanusov left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graziang thanks for recently add changes, it's almost ready, I've added multiple cosmetic things to update. Other then that, it should be fine. Good job, this is a big change 👍

Comment on lines +181 to +185
} finally {
requesterClient.updateWithCleanup(client -> client
.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not needed since, the updateWithCleanup is already used on the line 145

Suggested change
} finally {
requesterClient.updateWithCleanup(client -> client
.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
}

Comment on lines +217 to +218
requesterClient.updateWithCleanup(client -> client.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not needed since updateWithCleanup is already used on line 190.

Suggested change
requesterClient.updateWithCleanup(client -> client.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);

Comment on lines +137 to +141
} finally {
requesterClient.updateWithCleanup(client -> client
.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, cleaned already on the line 120

Suggested change
} finally {
requesterClient.updateWithCleanup(client -> client
.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
}

KeycloakUrls keycloakUrls;

@InjectSimpleHttp
org.keycloak.http.simple.SimpleHttp simpleHttp;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add import org.keycloak.http.simple.SimpleHttp;

Suggested change
org.keycloak.http.simple.SimpleHttp simpleHttp;
SimpleHttp simpleHttp;

Comment on lines +34 to +37
public UserRepresentation toRepresentation() {
return admin().toRepresentation();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, don't add unnecessary changes inside the framework

Suggested change
public UserRepresentation toRepresentation() {
return admin().toRepresentation();
}

Comment on lines +111 to +115
} finally {
requesterClient.updateWithCleanup(c-> c
.attribute(OIDCConfigAttributes.USE_REFRESH_TOKEN, Boolean.TRUE.toString())
.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name()));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, cleanup alread used on the line 101

Suggested change
} finally {
requesterClient.updateWithCleanup(c-> c
.attribute(OIDCConfigAttributes.USE_REFRESH_TOKEN, Boolean.TRUE.toString())
.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name()));
}

}

@Test
public void testOfflineAccessNotAllowed() throws Exception {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void testOfflineAccessNotAllowed() throws Exception {
public void testOfflineAccessNotAllowed() {

}

@Test
public void testOfflineAccessLoginWithRegularTokenExchange() throws Exception {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void testOfflineAccessLoginWithRegularTokenExchange() throws Exception {
public void testOfflineAccessLoginWithRegularTokenExchange() {

}

@Test
public void testOfflineAccessNotAllowedAfterOfflineAccessLogin() throws Exception {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void testOfflineAccessNotAllowedAfterOfflineAccessLogin() throws Exception {
public void testOfflineAccessNotAllowedAfterOfflineAccessLogin() {

Comment on lines +215 to +219
} finally {
oauth.scope("openid");
requesterClient.updateWithCleanup(client -> client.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed since updateWithCleanup is already used on line 190.
Is the oauth.scope("openid") required, since the default config is null and other test methods don't do any cleanup?

Suggested change
} finally {
oauth.scope("openid");
requesterClient.updateWithCleanup(client -> client.attribute(OIDCConfigAttributes.STANDARD_TOKEN_EXCHANGE_REFRESH_ENABLED, OIDCAdvancedConfigWrapper.TokenExchangeRefreshTokenEnabled.NO.name())
);
}

@lhanusov lhanusov left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graziang thanks for recently add changes, it's almost ready, I've added multiple cosmetic things to update. Other then that, it should be fine. Good job, this is a big change 👍

@mabartos
Copy link
Copy Markdown
Member

Hello! What's the progress here? We might be blocked by this eventually in a short-term. Thanks!

@graziang
Copy link
Copy Markdown
Contributor Author

Hello! What's the progress here? We might be blocked by this eventually in a short-term. Thanks!

@mabartos this PR is also blocked by two more PRs on the test framework:

@lhanusov Do you know when we can make som progress on this? Otherwise, we'll have to find another way to migrate these tests without the improvements to the test framework.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please. revert these changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, revert this change

Comment on lines +84 to +100
@InjectClient(ref = "subject-client", attachTo = "subject-client")
ManagedClient subjectClient;

@InjectClient(ref = "requester-client", attachTo = "requester-client")
ManagedClient requesterClient;

@InjectClient(ref = "requester-client-2", attachTo = "requester-client-2")
ManagedClient requesterClient2;

@InjectUser(ref = "john", attachTo = "john")
ManagedUser john;

@InjectUser(ref = "mike", attachTo = "mike")
ManagedUser mike;

@InjectUser(ref = "alice", attachTo = "alice")
ManagedUser alice;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of attachTo for clients and users, you will need to add UserConfig and ClientConfig and take it away from RealmConfig

10BC8 @graziang
Copy link
Copy Markdown
Contributor Author

@lhanusov rebased and hopefully addressed all the suggestions after our discussion.

@lhanusov lhanusov left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for updating this PR, only few changes left

*/
public class StandardTokenExchangeV2Test extends AbstractClientPoliciesTest {
@Deprecated
public abstract class StandardTokenExchangeV2Test extends AbstractClientPoliciesTest {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any tests left? If, no, you can remove this class. Don't worry, nobody will recreate it from scratch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StandardTokenExchangeV2WithLegacyTokenExchangeTest still extends StandardTokenExchangeV2Test, but it is deprecated and will be removed, the idea of ​​removing both then.

Comment on lines +330 to +358
public static class AliceConfig implements UserConfig {
@Override
public UserBuilder configure(UserBuilder user) {
return user.username("alice")
.name("Alice", "Doe")
.email("alice@email.cz")
.password("password");
}
}

public static class JohnConfig implements UserConfig {
@Override
public UserBuilder configure(UserBuilder user) {
return user.username("john")
.name("John", "Bar")
.email("john@email.cz")
.password("password");
}
}

public static class MikeConfig implements UserConfig {
@Override
public UserBuilder configure(UserBuilder user) {
return user.username("mike")
.name("Mike", "Bar")
.email("mike@email.cz")
.password("password");
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed

Suggested change
public static class AliceConfig implements UserConfig {
@Override
public UserBuilder configure(UserBuilder user) {
return user.username("alice")
.name("Alice", "Doe")
.email("alice@email.cz")
.password("password");
}
}
public static class JohnConfig implements UserConfig {
@Override
public UserBuilder configure(UserBuilder user) {
return user.username("john")
.name("John", "Bar")
.email("john@email.cz")
.password("password");
}
}
public static class MikeConfig implements UserConfig {
@Override
public UserBuilder configure(UserBuilder user) {
return user.username("mike")
.name("Mike", "Bar")
.email("mike@email.cz")
.password("password");
}
}

assertUserInfoError(exchangedTokenString, "requester-client", "secret", "invalid_token", Errors.USER_SESSION_NOT_FOUND);

realm.admin().clients().get(subjectClient.getId()).removeOptionalClientScope(offlineAccessScopeId);
timeOffSet.set(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed, since TimeOffset has LifeCycle.Method

realm.admin().deleteSession(exchangedToken.getSessionId(), false);
assertThrows(NotAuthorizedException.class, () -> keycloak.realm(realm.getName()).toRepresentation().getRealm());
}
timeOffSet.set(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed, since TimeOffset has LifeCycle.Method

fail("Error parsing response: ", e);
}
finally {
timeOffSet.set(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed, since TimeOffset has LifeCycle.Method

assertIntrospectSuccess(exchangedToken, "requester-client", "secret", mike.getId());
assertIntrospectSuccess(requesterClientToken, "requester-client", "secret", mike.getId());

timeOffSet.set(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed, since TimeOffset has LifeCycle.Method

@lhanusov lhanusov left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@graziang
Copy link
Copy Markdown
Contributor Author

@lhanusov Thanks for the review!

@mposolda now the PR should be ready, I marked StandardTokenExchangeV2Test as abstract and checked that is not executed in the ci.

@mabartos I added StandardBaseTokenExchangeWithDynamicScopesTest in the new testsuite to run token exchange tests with dynamic scope feature enabled.

@mabartos
Copy link
Copy Markdown
Member

@graziang Thanks! Updated the PR description to cover even the related issue for dynamic scopes.

graziang added 3 commits May 27, 2026 14:38
Closes keycloak#47491

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
Closes keycloak#47491

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
Closes keycloak#47491

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
@mposolda mposolda left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graziang @lhanusov Thanks! I hope to merge once tests are OK.

FYI. I've tried to manually rebase your remote branch to enforce GH CI running with this PR.

@mposolda
mposolda merged commit 2e99d2e into keycloak:main May 27, 2026
87 checks passed
@stianst stianst mentioned this pull request Jun 1, 2026
sashyo pushed a commit to tide-foundation/keycloak-IGA that referenced this pull request Jul 20, 2026
Closes keycloak#47491
closes keycloak#48982 


Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run Standard Token Exchange tests with dynamic client scopes enabled Migrate Standard Token Exchange tests

5 participants

0