[KEYCLOAK-16536] Implement Audit Events for Authorization Services requests
This commit is contained in:
parent
ed8d5a257f
commit
6255ebe6b5
8 changed files with 137 additions and 22 deletions
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue