[KEYCLOAK-13071] - AuthorizationTokenService swallows Exceptions thrown by KeycloakIdentity

This commit is contained in:
Pedro Igor 2020-03-03 16:46:48 -03:00 committed by Stian Thorgersen
parent 098ec91dd2
commit 44c49d69a7
3 changed files with 119 additions and 46 deletions

View file

@ -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);

View file

@ -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);
} }

View file

@ -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);