[KEYCLOAK-16536] Implement Audit Events for Authorization Services requests

This commit is contained in:
Juan Manuel Rodriguez Alvarado 2020-12-11 19:39:01 -06:00 committed by Pedro Igor
parent ed8d5a257f
commit 6255ebe6b5
8 changed files with 137 additions and 22 deletions

View file

@ -47,6 +47,7 @@ public interface Details {
String REASON = "reason"; String REASON = "reason";
String REVOKED_CLIENT = "revoked_client"; String REVOKED_CLIENT = "revoked_client";
String AUDIENCE = "audience"; String AUDIENCE = "audience";
String PERMISSION = "permission";
String SCOPE = "scope"; String SCOPE = "scope";
String REQUESTED_ISSUER = "requested_issuer"; String REQUESTED_ISSUER = "requested_issuer";
String REQUESTED_SUBJECT = "requested_subject"; String REQUESTED_SUBJECT = "requested_subject";

View file

@ -100,4 +100,7 @@ public interface Errors {
String INVALID_SAML_DOCUMENT = "invalid_saml_document"; String INVALID_SAML_DOCUMENT = "invalid_saml_document";
String UNSUPPORTED_NAMEID_FORMAT = "unsupported_nameid_format"; String UNSUPPORTED_NAMEID_FORMAT = "unsupported_nameid_format";
String INVALID_PERMISSION_TICKET = "invalid_permission_ticket";
String ACCESS_DENIED = "access_denied";
} }

View file

@ -174,19 +174,27 @@ public class AuthorizationTokenService {
} }
private static void fireErrorEvent(EventBuilder event, String error, Exception cause) { private static void fireErrorEvent(EventBuilder event, String error, Exception cause) {
event.detail(Details.REASON, cause == null || cause.getMessage() == null ? "<unknown>" : cause.getMessage()) if (cause instanceof CorsErrorResponseException) {
.error(error); // cast the exception to populate the event with a more descriptive reason
CorsErrorResponseException originalCause = (CorsErrorResponseException) cause;
event.detail(Details.REASON, originalCause.getErrorDescription() == null ? "<unknown>" : originalCause.getErrorDescription())
.error(error);
} else {
event.detail(Details.REASON, cause == null || cause.getMessage() == null ? "<unknown>" : cause.getMessage())
.error(error);
}
logger.debug(event.getEvent().getType(), cause); logger.debug(event.getEvent().getType(), cause);
} }
public Response authorize(KeycloakAuthorizationRequest request) { public Response authorize(KeycloakAuthorizationRequest request) {
if (request == null) { EventBuilder event = request.getEvent();
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
}
// it is not secure to allow public clients to push arbitrary claims because message can be tampered // it is not secure to allow public clients to push arbitrary claims because message can be tampered
if (isPublicClientRequestingEntitlementWithClaims(request)) { if (isPublicClientRequestingEntitlementWithClaims(request)) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN); CorsErrorResponseException forbiddenClientException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
fireErrorEvent(event, Errors.INVALID_REQUEST, forbiddenClientException);
throw forbiddenClientException;
} }
try { try {
@ -194,9 +202,15 @@ public class AuthorizationTokenService {
request.setClaims(ticket.getClaims()); request.setClaims(ticket.getClaims());
ResourceServer resourceServer = getResourceServer(ticket, request);
EvaluationContext evaluationContext = createEvaluationContext(request); EvaluationContext evaluationContext = createEvaluationContext(request);
KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity()); KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
if (identity != null) {
event.user(identity.getId());
}
ResourceServer resourceServer = getResourceServer(ticket, request);
Collection<Permission> permissions; Collection<Permission> permissions;
if (request.getTicket() != null) { if (request.getTicket() != null) {
@ -223,7 +237,9 @@ public class AuthorizationTokenService {
} else if (RESPONSE_MODE_PERMISSIONS.equals(metadata.getResponseMode())) { } else if (RESPONSE_MODE_PERMISSIONS.equals(metadata.getResponseMode())) {
return createSuccessfulResponse(permissions, request); return createSuccessfulResponse(permissions, request);
} else { } else {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Invalid response_mode", Status.BAD_REQUEST); CorsErrorResponseException invalidResponseModeException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Invalid response_mode", Status.BAD_REQUEST);
fireErrorEvent(event, Errors.INVALID_REQUEST, invalidResponseModeException);
throw invalidResponseModeException;
} }
} else { } else {
return createSuccessfulResponse(createAuthorizationResponse(identity, permissions, request, targetClient), request); return createSuccessfulResponse(createAuthorizationResponse(identity, permissions, request, targetClient), request);
@ -231,9 +247,13 @@ public class AuthorizationTokenService {
} }
if (request.isSubmitRequest()) { if (request.isSubmitRequest()) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN); CorsErrorResponseException submittedRequestException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
fireErrorEvent(event, Errors.ACCESS_DENIED, submittedRequestException);
throw submittedRequestException;
} else { } else {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN); CorsErrorResponseException notAuthorizedException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
fireErrorEvent(event, Errors.ACCESS_DENIED, notAuthorizedException);
throw notAuthorizedException;
} }
} catch (ErrorResponseException | CorsErrorResponseException cause) { } catch (ErrorResponseException | CorsErrorResponseException cause) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -402,19 +422,25 @@ public class AuthorizationTokenService {
String issuedFor = ticket.getIssuedFor(); String issuedFor = ticket.getIssuedFor();
if (issuedFor == null) { if (issuedFor == null) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "You must provide the issuedFor", Status.BAD_REQUEST); CorsErrorResponseException missingIssuedForException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "You must provide the issuedFor", Status.BAD_REQUEST);
fireErrorEvent(request.getEvent(), Errors.INVALID_REQUEST, missingIssuedForException);
throw missingIssuedForException;
} }
ClientModel clientModel = request.getRealm().getClientByClientId(issuedFor); ClientModel clientModel = request.getRealm().getClientByClientId(issuedFor);
if (clientModel == null) { if (clientModel == null) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Unknown resource server id.", Status.BAD_REQUEST); CorsErrorResponseException unknownServerIdException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Unknown resource server id: [" + issuedFor + "]", Status.BAD_REQUEST);
fireErrorEvent(request.getEvent(), Errors.INVALID_REQUEST, unknownServerIdException);
throw unknownServerIdException;
} }
ResourceServer resourceServer = resourceServerStore.findById(clientModel.getId()); ResourceServer resourceServer = resourceServerStore.findById(clientModel.getId());
if (resourceServer == null) { if (resourceServer == null) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.BAD_REQUEST); CorsErrorResponseException unsupportedPermissionsException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.BAD_REQUEST);
fireErrorEvent(request.getEvent(), Errors.INVALID_REQUEST, unsupportedPermissionsException);
throw unsupportedPermissionsException;
} }
return resourceServer; return resourceServer;
@ -430,7 +456,9 @@ public class AuthorizationTokenService {
BiFunction<KeycloakAuthorizationRequest, AuthorizationProvider, EvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat); BiFunction<KeycloakAuthorizationRequest, AuthorizationProvider, EvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
if (evaluationContextProvider == null) { if (evaluationContextProvider == null) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST); CorsErrorResponseException unsupportedClaimTokenFormatException = new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST);
fireErrorEvent(request.getEvent(), Errors.INVALID_REQUEST, unsupportedClaimTokenFormatException);
throw unsupportedClaimTokenFormatException;
} }
return evaluationContextProvider.apply(request, request.getAuthorization()); return evaluationContextProvider.apply(request, request.getAuthorization());
@ -636,7 +664,9 @@ public class AuthorizationTokenService {
} }
if (permissionsToEvaluate.isEmpty()) { if (permissionsToEvaluate.isEmpty()) {
throw new CorsErrorResponseException(request.getCors(), "invalid_resource", "Resource with id [" + resourceId + "] does not exist.", Status.BAD_REQUEST); CorsErrorResponseException invalidResourceException = new CorsErrorResponseException(request.getCors(), "invalid_resource", "Resource with id [" + resourceId + "] does not exist.", Status.BAD_REQUEST);
fireErrorEvent(request.getEvent(), Errors.INVALID_REQUEST, invalidResourceException);
throw invalidResourceException;
} }
} }
@ -657,7 +687,9 @@ public class AuthorizationTokenService {
Objects::nonNull).collect(Collectors.toSet()); Objects::nonNull).collect(Collectors.toSet());
if (!requestedScopes.isEmpty() && requestedScopesModel.isEmpty()) { if (!requestedScopes.isEmpty() && requestedScopesModel.isEmpty()) {
throw new CorsErrorResponseException(request.getCors(), "invalid_scope", "One of the given scopes " + permission.getScopes() + " is invalid", Status.BAD_REQUEST); CorsErrorResponseException invalidScopeException = new CorsErrorResponseException(request.getCors(), "invalid_scope", "One of the given scopes " + permission.getScopes() + " is invalid", Status.BAD_REQUEST);
fireErrorEvent(request.getEvent(), Errors.INVALID_REQUEST, invalidScopeException);
throw invalidScopeException;
} }
return requestedScopesModel; return requestedScopesModel;
} }
@ -690,11 +722,15 @@ public class AuthorizationTokenService {
PermissionTicketToken ticket = request.getKeycloakSession().tokens().decode(ticketString, PermissionTicketToken.class); PermissionTicketToken ticket = request.getKeycloakSession().tokens().decode(ticketString, PermissionTicketToken.class);
if (ticket == null) { if (ticket == null) {
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN); CorsErrorResponseException ticketVerificationException = new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
fireErrorEvent(request.getEvent(), Errors.INVALID_PERMISSION_TICKET, ticketVerificationException);
throw ticketVerificationException;
} }
if (!ticket.isActive()) { if (!ticket.isActive()) {
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN); CorsErrorResponseException invalidTicketException = new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
fireErrorEvent(request.getEvent(), Errors.INVALID_PERMISSION_TICKET, invalidTicketException);
throw invalidTicketException;
} }
return ticket; return ticket;

View file

@ -1244,8 +1244,10 @@ public class TokenEndpoint {
AccessToken invalidToken = new JWSInput(accessTokenString).readJsonContent(AccessToken.class); AccessToken invalidToken = new JWSInput(accessTokenString).readJsonContent(AccessToken.class);
ClientModel client = realm.getClientByClientId(invalidToken.getIssuedFor()); ClientModel client = realm.getClientByClientId(invalidToken.getIssuedFor());
cors.allowedOrigins(session, client); cors.allowedOrigins(session, client);
event.client(client);
} catch (JWSInputException ignore) { } catch (JWSInputException ignore) {
} }
event.error(Errors.INVALID_TOKEN);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid bearer token", Status.UNAUTHORIZED); throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid bearer token", Status.UNAUTHORIZED);
} }
@ -1254,6 +1256,7 @@ public class TokenEndpoint {
session.getContext().setClient(client); session.getContext().setClient(client);
cors.allowedOrigins(session, client); cors.allowedOrigins(session, client);
event.client(client);
} }
String claimToken = null; String claimToken = null;
@ -1300,6 +1303,7 @@ public class TokenEndpoint {
if (rpt != null) { if (rpt != null) {
AccessToken accessToken = session.tokens().decode(rpt, AccessToken.class); AccessToken accessToken = session.tokens().decode(rpt, AccessToken.class);
if (accessToken == null) { if (accessToken == null) {
event.error(Errors.INVALID_REQUEST);
throw new CorsErrorResponseException(cors, "invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN); throw new CorsErrorResponseException(cors, "invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
} }
@ -1307,9 +1311,12 @@ public class TokenEndpoint {
} }
authorizationRequest.setScope(formParams.getFirst("scope")); authorizationRequest.setScope(formParams.getFirst("scope"));
authorizationRequest.setAudience(formParams.getFirst("audience")); String audienceParam = formParams.getFirst("audience");
authorizationRequest.setAudience(audienceParam);
authorizationRequest.setSubjectToken(accessTokenString); authorizationRequest.setSubjectToken(accessTokenString);
event.detail(Details.AUDIENCE, audienceParam);
String submitRequest = formParams.getFirst("submit_request"); String submitRequest = formParams.getFirst("submit_request");
authorizationRequest.setSubmitRequest(submitRequest == null ? true : Boolean.valueOf(submitRequest)); authorizationRequest.setSubmitRequest(submitRequest == null ? true : Boolean.valueOf(submitRequest));
@ -1318,6 +1325,7 @@ public class TokenEndpoint {
List<String> permissions = formParams.get("permission"); List<String> permissions = formParams.get("permission");
if (permissions != null) { if (permissions != null) {
event.detail(Details.PERMISSION, String.join("|", permissions));
for (String permission : permissions) { for (String permission : permissions) {
String[] parts = permission.split("#"); String[] parts = permission.split("#");
String resource = parts[0]; String resource = parts[0];
@ -1349,7 +1357,11 @@ public class TokenEndpoint {
authorizationRequest.setMetadata(metadata); authorizationRequest.setMetadata(metadata);
return AuthorizationTokenService.instance().authorize(authorizationRequest); Response authorizationResponse = AuthorizationTokenService.instance().authorize(authorizationRequest);
event.success();
return authorizationResponse;
} }
// https://tools.ietf.org/html/rfc7636#section-4.1 // https://tools.ietf.org/html/rfc7636#section-4.1

View file

@ -41,6 +41,10 @@ public class CorsErrorResponseException extends WebApplicationException {
this.status = status; this.status = status;
} }
public String getErrorDescription() {
return errorDescription;
}
@Override @Override
public Response getResponse() { public Response getResponse() {
OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation(error, errorDescription); OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation(error, errorDescription);

View file

@ -77,6 +77,7 @@ public abstract class AbstractResourceServerTest extends AbstractAuthzTest {
.client(ClientBuilder.create().clientId("test-app") .client(ClientBuilder.create().clientId("test-app")
.redirectUris("http://localhost:8180/auth/realms/master/app/auth", "https://localhost:8543/auth/realms/master/app/auth") .redirectUris("http://localhost:8180/auth/realms/master/app/auth", "https://localhost:8543/auth/realms/master/app/auth")
.publicClient()) .publicClient())
.testEventListener()
.build()); .build());
} }

View file

@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.testsuite.AssertEvents.isUUID;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
@ -90,6 +91,7 @@ import org.keycloak.representations.idm.authorization.ResourceServerRepresentati
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
@ -125,6 +127,9 @@ public class EntitlementAPITest extends AbstractAuthzTest {
@Rule @Rule
public ExpectedException expectedException = ExpectedException.none(); public ExpectedException expectedException = ExpectedException.none();
@Rule
public AssertEvents events = new AssertEvents(this);
@Override @Override
public void addTestRealms(List<RealmRepresentation> testRealms) { public void addTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(RealmBuilder.create().name("authz-test") testRealms.add(RealmBuilder.create().name("authz-test")
@ -160,6 +165,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
.secret("secret") .secret("secret")
.redirectUris("http://localhost:8180/auth/realms/master/app/auth/*", "https://localhost:8543/auth/realms/master/app/auth/*") .redirectUris("http://localhost:8180/auth/realms/master/app/auth/*", "https://localhost:8543/auth/realms/master/app/auth/*")
.publicClient()) .publicClient())
.testEventListener()
.build()); .build());
configureSectorIdentifierRedirectUris(); configureSectorIdentifierRedirectUris();
@ -582,6 +588,8 @@ public class EntitlementAPITest extends AbstractAuthzTest {
request.addPermission("Sensortest", "sensors:view"); request.addPermission("Sensortest", "sensors:view");
getTestContext().getTestingClient().testing().clearEventQueue();
try { try {
authzClient.authorization(accessToken).authorize(request); authzClient.authorization(accessToken).authorize(request);
fail("resource is invalid"); fail("resource is invalid");
@ -589,6 +597,13 @@ public class EntitlementAPITest extends AbstractAuthzTest {
assertEquals(400, HttpResponseException.class.cast(expected.getCause()).getStatusCode()); assertEquals(400, HttpResponseException.class.cast(expected.getCause()).getStatusCode());
assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("invalid_resource")); assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("invalid_resource"));
} }
events.expect(EventType.PERMISSION_TOKEN_ERROR).realm(getRealm().toRepresentation().getId()).client(RESOURCE_SERVER_TEST)
.session((String) null)
.error("invalid_request")
.detail("reason", "Resource with id [Sensortest] does not exist.")
.user(isUUID())
.assertEvent();
} }
@Test @Test

View file

@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.testsuite.AssertEvents.isUUID;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,11 +29,13 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.authorization.client.AuthorizationDeniedException; import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.resource.PermissionResource; import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.events.EventType;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.AuthorizationRequest; import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationResponse; import org.keycloak.representations.idm.authorization.AuthorizationResponse;
@ -44,6 +47,7 @@ import org.keycloak.representations.idm.authorization.ResourcePermissionRepresen
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
@ -55,6 +59,9 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
private ResourceRepresentation resource; private ResourceRepresentation resource;
@Rule
public AssertEvents events = new AssertEvents(this);
@Before @Before
public void configureAuthorization() throws Exception { public void configureAuthorization() throws Exception {
ClientResource client = getClient(getRealm()); ClientResource client = getClient(getRealm());
@ -281,9 +288,12 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
permission.addResource(resource.getId()); permission.addResource(resource.getId());
permission.addPolicy("Only Owner Policy"); permission.addPolicy("Only Owner Policy");
getClient(getRealm()).authorization().permissions().resource().create(permission).close(); ClientResource client = getClient(getRealm());
client.authorization().permissions().resource().create(permission).close();
AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"}); AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
String rpt = response.getToken(); String rpt = response.getToken();
assertNotNull(rpt); assertNotNull(rpt);
@ -300,6 +310,8 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
assertTrue(permissions.isEmpty()); assertTrue(permissions.isEmpty());
getTestContext().getTestingClient().testing().clearEventQueue();
try { try {
response = authorize("kolo", "password", resource.getId(), new String[] {}); response = authorize("kolo", "password", resource.getId(), new String[] {});
fail("User should not have access to resource from another user"); fail("User should not have access to resource from another user");
@ -307,6 +319,22 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
} }
String realmId = getRealm().toRepresentation().getId();
String clientId = client.toRepresentation().getClientId();
events.expectLogin().realm(realmId).client(clientId)
.user(isUUID())
.clearDetails()
.assertEvent();
events.expectLogin().realm(realmId).client(clientId)
.user(isUUID())
.clearDetails()
.assertEvent();
events.expect(EventType.PERMISSION_TOKEN_ERROR).realm(realmId).client(clientId).user(isUUID())
.session((String) null)
.error("access_denied")
.detail("reason", "request_submitted")
.assertEvent();
PermissionResource permissionResource = getAuthzClient().protection().permission(); PermissionResource permissionResource = getAuthzClient().protection().permission();
List<PermissionTicketRepresentation> permissionTickets = permissionResource.findByResource(resource.getId()); List<PermissionTicketRepresentation> permissionTickets = permissionResource.findByResource(resource.getId());
@ -330,6 +358,8 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertTrue(ticket.isGranted()); assertTrue(ticket.isGranted());
} }
getTestContext().getTestingClient().testing().clearEventQueue();
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA", "ScopeB"}); response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA", "ScopeB"});
rpt = response.getToken(); rpt = response.getToken();
@ -346,6 +376,19 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB"); assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
assertTrue(permissions.isEmpty()); assertTrue(permissions.isEmpty());
events.expectLogin().realm(realmId).client(clientId)
.user(isUUID())
.clearDetails()
.assertEvent();
events.expectLogin().realm(realmId).client(clientId)
.user(isUUID())
.clearDetails()
.assertEvent();
events.expect(EventType.PERMISSION_TOKEN).realm(realmId).client(clientId).user(isUUID())
.session((String) null)
.clearDetails()
.assertEvent();
} }
@Test @Test