[KEYCLOAK-13071] - AuthorizationTokenService swallows Exceptions thrown by KeycloakIdentity
This commit is contained in:
parent
098ec91dd2
commit
44c49d69a7
3 changed files with 119 additions and 46 deletions
|
@ -58,6 +58,8 @@ import org.keycloak.authorization.store.StoreFactory;
|
||||||
import org.keycloak.authorization.util.Permissions;
|
import org.keycloak.authorization.util.Permissions;
|
||||||
import org.keycloak.authorization.util.Tokens;
|
import org.keycloak.authorization.util.Tokens;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -125,6 +127,7 @@ public class AuthorizationTokenService {
|
||||||
identity = new KeycloakIdentity(authorization.getKeycloakSession(),
|
identity = new KeycloakIdentity(authorization.getKeycloakSession(),
|
||||||
Tokens.getAccessToken(request.getSubjectToken(), authorization.getKeycloakSession()));
|
Tokens.getAccessToken(request.getSubjectToken(), authorization.getKeycloakSession()));
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
|
fireErrorEvent(request.getEvent(), Errors.INVALID_TOKEN, cause);
|
||||||
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid identity", Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid identity", Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +147,7 @@ public class AuthorizationTokenService {
|
||||||
try {
|
try {
|
||||||
idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, subjectToken);
|
idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, subjectToken);
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
|
fireErrorEvent(request.getEvent(), Errors.INVALID_SIGNATURE, cause);
|
||||||
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid signature", Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid signature", Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +156,7 @@ public class AuthorizationTokenService {
|
||||||
try {
|
try {
|
||||||
identity = new KeycloakIdentity(keycloakSession, idToken);
|
identity = new KeycloakIdentity(keycloakSession, idToken);
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
|
fireErrorEvent(request.getEvent(), Errors.INVALID_TOKEN, cause);
|
||||||
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid identity", Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid identity", Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +170,12 @@ public class AuthorizationTokenService {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void fireErrorEvent(EventBuilder event, String error, Exception cause) {
|
||||||
|
event.detail(Details.REASON, cause == null || cause.getMessage() == null ? "<unknown>" : cause.getMessage())
|
||||||
|
.error(error);
|
||||||
|
logger.debug(event.getEvent().getType(), cause);
|
||||||
|
}
|
||||||
|
|
||||||
public Response authorize(KeycloakAuthorizationRequest request) {
|
public Response authorize(KeycloakAuthorizationRequest request) {
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
|
||||||
|
|
|
@ -110,56 +110,60 @@ public class KeycloakIdentity implements Identity {
|
||||||
attributes.put(fieldName, values);
|
attributes.put(fieldName, values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token instanceof AccessToken) {
|
|
||||||
this.accessToken = AccessToken.class.cast(token);
|
|
||||||
} else {
|
|
||||||
UserSessionProvider sessions = keycloakSession.sessions();
|
|
||||||
UserSessionModel userSession = sessions.getUserSession(realm, token.getSessionState());
|
|
||||||
|
|
||||||
if (userSession == null) {
|
|
||||||
userSession = sessions.getOfflineUserSession(realm, token.getSessionState());
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
|
||||||
AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessions().get(client.getId());
|
|
||||||
|
|
||||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSessionModel, keycloakSession);
|
|
||||||
this.accessToken = new TokenManager().createClientAccessToken(keycloakSession, realm, client, userSession.getUser(), userSession, clientSessionCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessToken.Access realmAccess = this.accessToken.getRealmAccess();
|
|
||||||
|
|
||||||
if (realmAccess != null) {
|
|
||||||
attributes.put("kc.realm.roles", realmAccess.getRoles());
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, AccessToken.Access> resourceAccess = this.accessToken.getResourceAccess();
|
|
||||||
|
|
||||||
if (resourceAccess != null) {
|
|
||||||
resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientModel clientModel = getTargetClient();
|
|
||||||
UserModel clientUser = null;
|
|
||||||
|
|
||||||
if (clientModel != null) {
|
|
||||||
clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserModel userSession = getUserFromSessionState();
|
|
||||||
|
|
||||||
this.resourceServer = clientUser != null && userSession.getId().equals(clientUser.getId());
|
|
||||||
|
|
||||||
if (resourceServer) {
|
|
||||||
this.id = clientModel.getId();
|
|
||||||
} else {
|
|
||||||
this.id = userSession.getId();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error while reading attributes from security token.", e);
|
throw new RuntimeException("Error while reading attributes from security token.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token instanceof AccessToken) {
|
||||||
|
this.accessToken = AccessToken.class.cast(token);
|
||||||
|
} else {
|
||||||
|
UserSessionProvider sessions = keycloakSession.sessions();
|
||||||
|
UserSessionModel userSession = sessions.getUserSession(realm, token.getSessionState());
|
||||||
|
|
||||||
|
if (userSession == null) {
|
||||||
|
userSession = sessions.getOfflineUserSession(realm, token.getSessionState());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userSession == null) {
|
||||||
|
throw new RuntimeException("No active session associated with the token");
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||||
|
AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessions().get(client.getId());
|
||||||
|
|
||||||
|
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSessionModel, keycloakSession);
|
||||||
|
this.accessToken = new TokenManager().createClientAccessToken(keycloakSession, realm, client, userSession.getUser(), userSession, clientSessionCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.Access realmAccess = this.accessToken.getRealmAccess();
|
||||||
|
|
||||||
|
if (realmAccess != null) {
|
||||||
|
attributes.put("kc.realm.roles", realmAccess.getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, AccessToken.Access> resourceAccess = this.accessToken.getResourceAccess();
|
||||||
|
|
||||||
|
if (resourceAccess != null) {
|
||||||
|
resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientModel clientModel = getTargetClient();
|
||||||
|
UserModel clientUser = null;
|
||||||
|
|
||||||
|
if (clientModel != null) {
|
||||||
|
clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel userSession = getUserFromSessionState();
|
||||||
|
|
||||||
|
this.resourceServer = clientUser != null && userSession.getId().equals(clientUser.getId());
|
||||||
|
|
||||||
|
if (resourceServer) {
|
||||||
|
this.id = clientModel.getId();
|
||||||
|
} else {
|
||||||
|
this.id = userSession.getId();
|
||||||
|
}
|
||||||
|
|
||||||
this.attributes = Attributes.from(attributes);
|
this.attributes = Attributes.from(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ import org.keycloak.authorization.client.Configuration;
|
||||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
||||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
|
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
|
||||||
|
@ -70,7 +71,9 @@ import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessToken.Authorization;
|
import org.keycloak.representations.AccessToken.Authorization;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
||||||
|
@ -1930,6 +1933,61 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidTokenSignature() throws Exception {
|
||||||
|
RealmEventsConfigRepresentation eventConfig = getRealm().getRealmEventsConfig();
|
||||||
|
|
||||||
|
eventConfig.setEventsEnabled(true);
|
||||||
|
eventConfig.setEnabledEventTypes(Arrays.asList(EventType.PERMISSION_TOKEN_ERROR.name()));
|
||||||
|
|
||||||
|
getRealm().updateRealmEventsConfig(eventConfig);
|
||||||
|
|
||||||
|
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
|
||||||
|
AuthorizationResource authorization = client.authorization();
|
||||||
|
|
||||||
|
JSPolicyRepresentation policy = new JSPolicyRepresentation();
|
||||||
|
|
||||||
|
policy.setName(KeycloakModelUtils.generateId());
|
||||||
|
policy.setCode("$evaluation.grant();");
|
||||||
|
|
||||||
|
authorization.policies().js().create(policy).close();
|
||||||
|
|
||||||
|
ResourceRepresentation resource = new ResourceRepresentation();
|
||||||
|
|
||||||
|
resource.setName("Sensors");
|
||||||
|
|
||||||
|
try (Response response = authorization.resources().create(resource)) {
|
||||||
|
response.readEntity(ResourceRepresentation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||||
|
|
||||||
|
permission.setName("View Sensor");
|
||||||
|
permission.addPolicy(policy.getName());
|
||||||
|
|
||||||
|
authorization.permissions().resource().create(permission).close();
|
||||||
|
|
||||||
|
String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
|
||||||
|
AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG);
|
||||||
|
AuthorizationRequest request = new AuthorizationRequest();
|
||||||
|
|
||||||
|
request.addPermission("Sensors");
|
||||||
|
request.setSubjectToken(accessToken + "i");
|
||||||
|
|
||||||
|
try {
|
||||||
|
authzClient.authorization().authorize(request);
|
||||||
|
fail("should fail, session invalidated");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwable expected = e.getCause();
|
||||||
|
assertEquals(400, HttpResponseException.class.cast(expected).getStatusCode());
|
||||||
|
assertTrue(HttpResponseException.class.cast(expected).toString().contains("unauthorized_client"));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EventRepresentation> events = getRealm()
|
||||||
|
.getEvents(Arrays.asList(EventType.PERMISSION_TOKEN_ERROR.name()), null, null, null, null, null, null, null);
|
||||||
|
assertEquals(1, events.size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDenyScopeNotManagedByScopePolicy() throws Exception {
|
public void testDenyScopeNotManagedByScopePolicy() throws Exception {
|
||||||
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
|
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
|
||||||
|
|
Loading…
Reference in a new issue