Allow token exchange when subjec_token is not associated with a session

Closes #12596
This commit is contained in:
Pedro Igor 2022-06-28 11:28:48 -03:00
parent 4fd3049c85
commit 3631a413d2
2 changed files with 39 additions and 0 deletions

View file

@ -29,6 +29,7 @@ import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.IdentityProviderMapperSyncModeDelegate;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.common.util.Base64Url;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@ -370,6 +371,13 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
if (targetUserSession == null) {
// if no session is associated with a subject_token, a stateless session is created to only allow building a token to the audience
targetUserSession = session.sessions().createUserSession(authSession.getParentSession().getId(), realm, targetUser, targetUser.getUsername(),
clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT);
}
event.session(targetUserSession);
AuthenticationManager.setClientScopesInSession(authSession);

View file

@ -209,6 +209,15 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
noRefreshToken.setFullScopeAllowed(false);
noRefreshToken.getAttributes().put(OIDCConfigAttributes.USE_REFRESH_TOKEN, "false");
ClientModel serviceAccount = realm.addClient("my-service-account");
serviceAccount.setClientId("my-service-account");
serviceAccount.setPublicClient(false);
serviceAccount.setServiceAccountsEnabled(true);
serviceAccount.setEnabled(true);
serviceAccount.setSecret("secret");
serviceAccount.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
serviceAccount.setFullScopeAllowed(false);
// permission for client to client exchange to "target" client
ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
clientRep.setName("to");
@ -216,6 +225,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
clientRep.addClient(legal.getId());
clientRep.addClient(directLegal.getId());
clientRep.addClient(noRefreshToken.getId());
clientRep.addClient(serviceAccount.getId());
ResourceServer server = management.realmResourceServer();
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(server, clientRep);
@ -305,6 +315,27 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
}
}
@Test
@UncaughtServerErrorExpected
public void testExchangeUsingServiceAccount() throws Exception {
testingClient.server().run(ClientTokenExchangeTest::setupRealm);
oauth.realm(TEST);
oauth.clientId("my-service-account");
OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("secret");
String accessToken = response.getAccessToken();
{
response = oauth.doTokenExchange(TEST, accessToken, "target", "my-service-account", "secret");
String exchangedTokenString = response.getAccessToken();
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
AccessToken exchangedToken = verifier.parse().getToken();
Assert.assertEquals("my-service-account", exchangedToken.getIssuedFor());
Assert.assertEquals("target", exchangedToken.getAudience()[0]);
Assert.assertEquals(exchangedToken.getPreferredUsername(), "service-account-my-service-account");
}
}
@Test
@UncaughtServerErrorExpected
public void testExchangeFromPublicClient() throws Exception {