Admin API with Lightweight access token and transient session
Closes #32802 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
parent
9c780e9190
commit
e6c5ee31e4
4 changed files with 57 additions and 16 deletions
|
@ -35,6 +35,7 @@ import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.saml.common.util.StringUtil;
|
import org.keycloak.saml.common.util.StringUtil;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
@ -136,6 +137,10 @@ public class KeycloakIdentity implements Identity {
|
||||||
throw new RuntimeException("No active session associated with the token");
|
throw new RuntimeException("No active session associated with the token");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AuthenticationManager.isSessionValid(realm, userSession) && token.isIssuedBeforeSessionStart(userSession.getStarted())) {
|
||||||
|
throw new RuntimeException("Invalid token");
|
||||||
|
}
|
||||||
|
|
||||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||||
AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||||
|
|
||||||
|
@ -307,7 +312,9 @@ public class KeycloakIdentity implements Identity {
|
||||||
if (userSession == null) {
|
if (userSession == null) {
|
||||||
userSession = sessions.getOfflineUserSession(realm, accessToken.getSessionState());
|
userSession = sessions.getOfflineUserSession(realm, accessToken.getSessionState());
|
||||||
}
|
}
|
||||||
|
if (AuthenticationManager.isSessionValid(realm, userSession) && accessToken.isIssuedBeforeSessionStart(userSession.getStarted())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return userSession.getUser();
|
return userSession.getUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ import org.keycloak.authorization.model.Scope;
|
||||||
import org.keycloak.authorization.permission.ResourcePermission;
|
import org.keycloak.authorization.permission.ResourcePermission;
|
||||||
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AdminRoles;
|
import org.keycloak.models.AdminRoles;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -52,6 +54,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import jakarta.ws.rs.ForbiddenException;
|
import jakarta.ws.rs.ForbiddenException;
|
||||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||||
|
import org.keycloak.services.util.UserSessionUtil;
|
||||||
import org.keycloak.utils.RoleResolveUtil;
|
import org.keycloak.utils.RoleResolveUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,20 +110,17 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
|
||||||
private void initIdentity(KeycloakSession session, AdminAuth auth) {
|
private void initIdentity(KeycloakSession session, AdminAuth auth) {
|
||||||
final String issuedFor = auth.getToken().getIssuedFor();
|
final String issuedFor = auth.getToken().getIssuedFor();
|
||||||
AccessToken accessToken = auth.getToken();
|
AccessToken accessToken = auth.getToken();
|
||||||
//support for lightweight access token
|
ClientModel client = adminsRealm.getClientByClientId(issuedFor);
|
||||||
if (auth.getToken().getSubject() == null) {
|
//support for lightweight access token and transient session
|
||||||
|
if (accessToken.getSubject() == null || (accessToken.getSessionId() == null && accessToken.getResourceAccess().isEmpty() && accessToken.getRealmAccess() == null)) {
|
||||||
//get user session
|
//get user session
|
||||||
UserSessionProvider sessions = session.sessions();
|
EventBuilder event = new EventBuilder(adminsRealm, session);
|
||||||
UserSessionModel userSession = sessions.getUserSession(adminsRealm, auth.getToken().getSessionId());
|
event.event(EventType.INTROSPECT_TOKEN);
|
||||||
if (userSession == null) {
|
UserSessionModel userSession = UserSessionUtil.findValidSession(session, adminsRealm, accessToken, event, client);
|
||||||
userSession = sessions.getOfflineUserSession(adminsRealm, auth.getToken().getSessionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userSession != null) {
|
if (userSession != null) {
|
||||||
//get client session
|
//get client session
|
||||||
ClientModel client = adminsRealm.getClientByClientId(issuedFor);
|
|
||||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||||
|
|
||||||
//set realm roles
|
//set realm roles
|
||||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, auth.getToken().getScope(), session);
|
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, auth.getToken().getScope(), session);
|
||||||
AccessToken.Access realmAccess = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false);
|
AccessToken.Access realmAccess = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false);
|
||||||
|
|
|
@ -700,12 +700,9 @@ public abstract class AbstractKeycloakTest {
|
||||||
Time.setOffset(offset);
|
Time.setOffset(offset);
|
||||||
Map result = testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset)));
|
Map result = testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset)));
|
||||||
|
|
||||||
// force refreshing token after time offset has changed
|
// force getting new token after time offset has changed
|
||||||
try {
|
adminClient.tokenManager().grantToken();
|
||||||
adminClient.tokenManager().refreshToken();
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
adminClient.tokenManager().grantToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.valueOf(result);
|
return String.valueOf(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.admin.client.resource.ClientScopeResource;
|
||||||
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.models.AdminRoles;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
@ -68,6 +69,7 @@ import org.keycloak.utils.MediaType;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -521,6 +523,41 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAdminApiWithLightweightAccessTokenAndTransientSession() {
|
||||||
|
RealmResource masterRealm = realmsResouce().realm("master");
|
||||||
|
ClientRepresentation transientClient = KeycloakModelUtils.createClient(realmsResouce().realm("master").toRepresentation(), "transient_client");
|
||||||
|
transientClient.setServiceAccountsEnabled(Boolean.TRUE);
|
||||||
|
transientClient.setAttributes(new HashMap<>());
|
||||||
|
transientClient.getAttributes().put(Constants.USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED, String.valueOf(true));
|
||||||
|
masterRealm.clients().create(transientClient);
|
||||||
|
transientClient = masterRealm.clients().findByClientId(transientClient.getClientId()).get(0);
|
||||||
|
|
||||||
|
UserRepresentation userRep = masterRealm.clients().get(transientClient.getId()).getServiceAccountUser();
|
||||||
|
masterRealm.users().get(userRep.getId()).roles().realmLevel().add(Collections.singletonList(masterRealm.roles().get(AdminRoles.ADMIN).toRepresentation()));
|
||||||
|
try {
|
||||||
|
oauth.realm("master");
|
||||||
|
oauth.clientId(transientClient.getClientId());
|
||||||
|
OAuthClient.AccessTokenResponse tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest(transientClient.getSecret());
|
||||||
|
String accessTokenString = tokenResponse.getAccessToken();
|
||||||
|
Assert.assertNull(tokenResponse.getRefreshToken());
|
||||||
|
AccessToken accessToken = oauth.verifyToken(accessTokenString);
|
||||||
|
Assert.assertNotNull(accessToken.getSubject());
|
||||||
|
Assert.assertNull(accessToken.getSessionId());
|
||||||
|
|
||||||
|
CloseableHttpClient client = HttpClientBuilder.create().build();
|
||||||
|
HttpGet get = new HttpGet(OAuthClient.SERVER_ROOT + "/auth/admin/realms/master");
|
||||||
|
get.setHeader("Authorization", "Bearer " + accessTokenString);
|
||||||
|
CloseableHttpResponse response = client.execute(get);
|
||||||
|
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
RealmRepresentation realmRepresentation = JsonSerialization.readValue(response.getEntity().getContent(), RealmRepresentation.class);
|
||||||
|
Assert.assertEquals("master", realmRepresentation.getRealm());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void removeSession(final String sessionId) {
|
private void removeSession(final String sessionId) {
|
||||||
testingClient.testing().removeExpired(REALM_NAME);
|
testingClient.testing().removeExpired(REALM_NAME);
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue