diff --git a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java index cba5ed6672..128b34451e 100644 --- a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java +++ b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java @@ -156,5 +156,25 @@ public enum ResourceType { /** * */ - , COMPONENT; + , COMPONENT + + /** + * + */ + , AUTHORIZATION_RESOURCE_SERVER + + /** + * + */ + , AUTHORIZATION_RESOURCE + + /** + * + */ + , AUTHORIZATION_SCOPE + + /** + * + */ + , AUTHORIZATION_POLICY; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 7431ed2cc0..bdde153c40 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -772,11 +772,7 @@ public class ModelToRepresentation { return rep; } - public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider) { - return toRepresentation(model, authorizationProvider, true); - } - - public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider, boolean deep) { + public static ScopeRepresentation toRepresentation(Scope model) { ScopeRepresentation scope = new ScopeRepresentation(); scope.setId(model.getId()); diff --git a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java index a4ff868276..bc9dd1f19f 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java @@ -23,6 +23,7 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import javax.ws.rs.Path; @@ -34,14 +35,14 @@ public class AuthorizationService { private final RealmAuth auth; private final ClientModel client; - private final KeycloakSession session; private final ResourceServer resourceServer; private final AuthorizationProvider authorization; + private final AdminEventBuilder adminEvent; - public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth) { - this.session = session; + public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth, AdminEventBuilder adminEvent) { this.client = client; this.authorization = session.getProvider(AuthorizationProvider.class); + this.adminEvent = adminEvent; this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().findByClient(this.client.getId()); this.auth = auth; @@ -52,16 +53,16 @@ public class AuthorizationService { @Path("/resource-server") public ResourceServerService resourceServer() { - ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth); + ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; } - public void enable() { + public void enable(boolean newClient) { if (!isEnabled()) { - resourceServer().create(); + resourceServer().create(newClient); } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java index ba4fae255d..ea4677da37 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java @@ -22,6 +22,7 @@ import java.util.Map; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; /** @@ -29,18 +30,18 @@ import org.keycloak.services.resources.admin.RealmAuth; */ public class PermissionService extends PolicyService { - public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { - super(resourceServer, authorization, auth); + public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { + super(resourceServer, authorization, auth, adminEvent); } @Override protected PolicyResourceService doCreatePolicyResource(Policy policy) { - return new PolicyTypeResourceService(policy, resourceServer, authorization, auth); + return new PolicyTypeResourceService(policy, resourceServer, authorization, auth, adminEvent); } @Override protected PolicyTypeService doCreatePolicyTypeResource(String type) { - return new PolicyTypeService(type, resourceServer, authorization, auth) { + return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent) { @Override protected List doSearch(Integer firstResult, Integer maxResult, Map filters) { filters.put("permission", new String[] {Boolean.TRUE.toString()}); diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java index 85e0943221..c0728bc423 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java @@ -26,8 +26,10 @@ import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.authorization.AuthorizationProvider; @@ -36,12 +38,15 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import org.keycloak.util.JsonSerialization; @@ -54,19 +59,21 @@ public class PolicyResourceService { protected final ResourceServer resourceServer; protected final AuthorizationProvider authorization; protected final RealmAuth auth; + private final AdminEventBuilder adminEvent; - public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { this.policy = policy; this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; + this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_POLICY); } @PUT @Consumes("application/json") @Produces("application/json") @NoCache - public Response update(String payload) { + public Response update(@Context UriInfo uriInfo, String payload) { this.auth.requireManage(); AbstractPolicyRepresentation representation = doCreateRepresentation(payload); @@ -79,11 +86,14 @@ public class PolicyResourceService { RepresentationToModel.toModel(representation, authorization, policy); + + audit(uriInfo, representation, OperationType.UPDATE); + return Response.status(Status.CREATED).build(); } @DELETE - public Response delete() { + public Response delete(@Context UriInfo uriInfo) { this.auth.requireManage(); if (policy == null) { @@ -98,6 +108,10 @@ public class PolicyResourceService { policyStore.delete(policy.getId()); + if (authorization.getRealm().isAdminEventsEnabled()) { + audit(uriInfo, toRepresentation(policy, authorization), OperationType.DELETE); + } + return Response.noContent().build(); } @@ -225,4 +239,10 @@ public class PolicyResourceService { protected Policy getPolicy() { return policy; } + + private void audit(@Context UriInfo uriInfo, AbstractPolicyRepresentation policy, OperationType operation) { + if (authorization.getRealm().isAdminEventsEnabled()) { + adminEvent.operation(operation).resourcePath(uriInfo).representation(policy).success(); + } + } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index e6700422a6..e8d4aa8455 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -33,9 +33,11 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.ResteasyProviderFactory; @@ -46,12 +48,16 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; import org.keycloak.models.Constants; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import org.keycloak.util.JsonSerialization; @@ -63,11 +69,13 @@ public class PolicyService { protected final ResourceServer resourceServer; protected final AuthorizationProvider authorization; protected final RealmAuth auth; + protected final AdminEventBuilder adminEvent; - public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; + this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_POLICY); } @Path("{type}") @@ -84,18 +92,18 @@ public class PolicyService { } protected PolicyTypeService doCreatePolicyTypeResource(String type) { - return new PolicyTypeService(type, resourceServer, authorization, auth); + return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent); } protected Object doCreatePolicyResource(Policy policy) { - return new PolicyResourceService(policy, resourceServer, authorization, auth); + return new PolicyResourceService(policy, resourceServer, authorization, auth, adminEvent); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @NoCache - public Response create(String payload) { + public Response create(@Context UriInfo uriInfo, String payload) { this.auth.requireManage(); AbstractPolicyRepresentation representation = doCreateRepresentation(payload); @@ -103,6 +111,8 @@ public class PolicyService { representation.setId(policy.getId()); + audit(uriInfo, representation, representation.getId(), OperationType.CREATE); + return Response.status(Status.CREATED).entity(representation).build(); } @@ -280,4 +290,14 @@ public class PolicyService { findAssociatedPolicies(associated, policies); }); } + + private void audit(@Context UriInfo uriInfo, AbstractPolicyRepresentation resource, String id, OperationType operation) { + if (authorization.getRealm().isAdminEventsEnabled()) { + if (id != null) { + adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success(); + } else { + adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success(); + } + } + } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java index 8756721e9d..9044c0d75e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java @@ -24,6 +24,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import org.keycloak.util.JsonSerialization; @@ -32,8 +33,8 @@ import org.keycloak.util.JsonSerialization; */ public class PolicyTypeResourceService extends PolicyResourceService { - public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { - super(policy, resourceServer, authorization, auth); + public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { + super(policy, resourceServer, authorization, auth, adminEvent); } @Override diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java index 5f03dad3f4..6f37f6c99e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java @@ -30,6 +30,7 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import org.keycloak.util.JsonSerialization; @@ -40,8 +41,8 @@ public class PolicyTypeService extends PolicyService { private final String type; - PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { - super(resourceServer, authorization, auth); + PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { + super(resourceServer, authorization, auth, adminEvent); this.type = type; } @@ -60,7 +61,7 @@ public class PolicyTypeService extends PolicyService { @Override protected Object doCreatePolicyResource(Policy policy) { - return new PolicyTypeResourceService(policy, resourceServer,authorization, auth); + return new PolicyTypeResourceService(policy, resourceServer,authorization, auth, adminEvent); } @Override diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java index 15f1db7a52..777d84366b 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java @@ -40,12 +40,15 @@ import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; import org.keycloak.exportimport.util.ExportUtils; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; @@ -53,6 +56,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; /** @@ -62,19 +66,24 @@ public class ResourceServerService { private final AuthorizationProvider authorization; private final RealmAuth auth; + private final AdminEventBuilder adminEvent; private final KeycloakSession session; private ResourceServer resourceServer; private final ClientModel client; - public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, RealmAuth auth) { + @Context + private UriInfo uriInfo; + + public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, RealmAuth auth, AdminEventBuilder adminEvent) { this.authorization = authorization; this.session = authorization.getKeycloakSession(); this.client = client; this.resourceServer = resourceServer; this.auth = auth; + this.adminEvent = adminEvent; } - public void create() { + public void create(boolean newClient) { this.auth.requireManage(); UserModel serviceAccount = this.session.users().getServiceAccount(client); @@ -86,16 +95,17 @@ public class ResourceServerService { this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().create(this.client.getId()); createDefaultRoles(serviceAccount); createDefaultPermission(createDefaultResource(), createDefaultPolicy()); + audit(OperationType.CREATE, uriInfo, newClient); } @PUT @Consumes("application/json") @Produces("application/json") - public Response update(ResourceServerRepresentation server) { + public Response update(@Context UriInfo uriInfo, ResourceServerRepresentation server) { this.auth.requireManage(); this.resourceServer.setAllowRemoteResourceManagement(server.isAllowRemoteResourceManagement()); this.resourceServer.setPolicyEnforcementMode(server.getPolicyEnforcementMode()); - + audit(OperationType.UPDATE, uriInfo, false); return Response.noContent().build(); } @@ -105,17 +115,19 @@ public class ResourceServerService { ResourceStore resourceStore = storeFactory.getResourceStore(); String id = resourceServer.getId(); + PolicyStore policyStore = storeFactory.getPolicyStore(); + + policyStore.findByResourceServer(id).forEach(scope -> policyStore.delete(scope.getId())); + resourceStore.findByResourceServer(id).forEach(resource -> resourceStore.delete(resource.getId())); ScopeStore scopeStore = storeFactory.getScopeStore(); scopeStore.findByResourceServer(id).forEach(scope -> scopeStore.delete(scope.getId())); - PolicyStore policyStore = storeFactory.getPolicyStore(); - - policyStore.findByResourceServer(id).forEach(scope -> policyStore.delete(scope.getId())); - storeFactory.getResourceServerStore().delete(id); + + audit(OperationType.DELETE, uriInfo, false); } @GET @@ -143,12 +155,14 @@ public class ResourceServerService { RepresentationToModel.toModel(rep, authorization); + audit(OperationType.UPDATE, uriInfo, false); + return Response.noContent().build(); } @Path("/resource") public ResourceSetService getResourceSetResource() { - ResourceSetService resource = new ResourceSetService(this.resourceServer, this.authorization, this.auth); + ResourceSetService resource = new ResourceSetService(this.resourceServer, this.authorization, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); @@ -157,7 +171,7 @@ public class ResourceServerService { @Path("/scope") public ScopeService getScopeResource() { - ScopeService resource = new ScopeService(this.resourceServer, this.authorization, this.auth); + ScopeService resource = new ScopeService(this.resourceServer, this.authorization, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); @@ -166,7 +180,7 @@ public class ResourceServerService { @Path("/policy") public PolicyService getPolicyResource() { - PolicyService resource = new PolicyService(this.resourceServer, this.authorization, this.auth); + PolicyService resource = new PolicyService(this.resourceServer, this.authorization, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); @@ -176,7 +190,7 @@ public class ResourceServerService { @Path("/permission") public Object getPermissionTypeResource() { this.auth.requireView(); - PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth); + PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); @@ -239,4 +253,14 @@ public class ResourceServerService { serviceAccount.grantRole(umaProtectionRole); } } + + private void audit(OperationType operation, UriInfo uriInfo, boolean newClient) { + if (newClient) { + adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE_SERVER).operation(operation).resourcePath(uriInfo, client.getId()) + .representation(ModelToRepresentation.toRepresentation(resourceServer, client)).success(); + } else { + adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE_SERVER).operation(operation).resourcePath(uriInfo) + .representation(ModelToRepresentation.toRepresentation(resourceServer, client)).success(); + } + } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index 5d6592366d..b0666f1144 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -26,6 +26,8 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; @@ -37,6 +39,7 @@ import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentatio import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import javax.ws.rs.Consumes; @@ -48,8 +51,10 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.Collections; @@ -70,17 +75,25 @@ public class ResourceSetService { private final AuthorizationProvider authorization; private final RealmAuth auth; + private final AdminEventBuilder adminEvent; private ResourceServer resourceServer; - public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; + this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE); } @POST @Consumes("application/json") @Produces("application/json") + public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) { + Response response = create(resource); + audit(uriInfo, resource, resource.getId(), OperationType.CREATE); + return response; + } + public Response create(ResourceRepresentation resource) { requireManage(); StoreFactory storeFactory = this.authorization.getStoreFactory(); @@ -128,7 +141,7 @@ public class ResourceSetService { @PUT @Consumes("application/json") @Produces("application/json") - public Response update(@PathParam("id") String id, ResourceRepresentation resource) { + public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ResourceRepresentation resource) { requireManage(); resource.setId(id); StoreFactory storeFactory = this.authorization.getStoreFactory(); @@ -141,12 +154,14 @@ public class ResourceSetService { toModel(resource, resourceServer, authorization); + audit(uriInfo, resource, OperationType.UPDATE); + return Response.noContent().build(); } @Path("{id}") @DELETE - public Response delete(@PathParam("id") String id) { + public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) { requireManage(); StoreFactory storeFactory = authorization.getStoreFactory(); Resource resource = storeFactory.getResourceStore().findById(id, resourceServer.getId()); @@ -168,6 +183,10 @@ public class ResourceSetService { storeFactory.getResourceStore().delete(id); + if (authorization.getRealm().isAdminEventsEnabled()) { + audit(uriInfo, toRepresentation(resource, resourceServer, authorization), OperationType.DELETE); + } + return Response.noContent().build(); } @@ -376,4 +395,18 @@ public class ResourceSetService { this.auth.requireManage(); } } + + private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, OperationType operation) { + audit(uriInfo, resource, null, operation); + } + + private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) { + if (authorization.getRealm().isAdminEventsEnabled()) { + if (id != null) { + adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success(); + } else { + adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success(); + } + } + } } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index 1ec9a139e0..d59a6de32e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -25,11 +25,14 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; import org.keycloak.models.Constants; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.RealmAuth; import javax.ws.rs.Consumes; @@ -41,9 +44,11 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; import java.util.Arrays; import java.util.HashMap; @@ -61,23 +66,27 @@ public class ScopeService { private final AuthorizationProvider authorization; private final RealmAuth auth; + private final AdminEventBuilder adminEvent; private ResourceServer resourceServer; - public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; + this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_SCOPE); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Response create(ScopeRepresentation scope) { + public Response create(@Context UriInfo uriInfo, ScopeRepresentation scope) { this.auth.requireManage(); Scope model = toModel(scope, this.resourceServer, authorization); scope.setId(model.getId()); + audit(uriInfo, scope, scope.getId(), OperationType.CREATE); + return Response.status(Status.CREATED).entity(scope).build(); } @@ -85,7 +94,7 @@ public class ScopeService { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Response update(@PathParam("id") String id, ScopeRepresentation scope) { + public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ScopeRepresentation scope) { this.auth.requireManage(); scope.setId(id); StoreFactory storeFactory = authorization.getStoreFactory(); @@ -97,12 +106,14 @@ public class ScopeService { toModel(scope, resourceServer, authorization); + audit(uriInfo, scope, OperationType.UPDATE); + return Response.noContent().build(); } @Path("{id}") @DELETE - public Response delete(@PathParam("id") String id) { + public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) { this.auth.requireManage(); StoreFactory storeFactory = authorization.getStoreFactory(); List resources = storeFactory.getResourceStore().findByScope(Arrays.asList(id), resourceServer.getId()); @@ -130,6 +141,10 @@ public class ScopeService { storeFactory.getScopeStore().delete(id); + if (authorization.getRealm().isAdminEventsEnabled()) { + audit(uriInfo, toRepresentation(scope), OperationType.DELETE); + } + return Response.noContent().build(); } @@ -144,7 +159,7 @@ public class ScopeService { return Response.status(Status.NOT_FOUND).build(); } - return Response.ok(toRepresentation(model, this.authorization)).build(); + return Response.ok(toRepresentation(model)).build(); } @Path("{id}/resources") @@ -212,22 +227,17 @@ public class ScopeService { return Response.status(Status.OK).build(); } - return Response.ok(toRepresentation(model, authorization)).build(); + return Response.ok(toRepresentation(model)).build(); } @GET @Produces("application/json") public Response findAll(@QueryParam("scopeId") String id, @QueryParam("name") String name, - @QueryParam("deep") Boolean deep, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResult) { this.auth.requireView(); - if (deep == null) { - deep = true; - } - Map search = new HashMap<>(); if (id != null && !"".equals(id.trim())) { @@ -238,11 +248,24 @@ public class ScopeService { search.put("name", new String[] {name}); } - Boolean finalDeep = deep; return Response.ok( this.authorization.getStoreFactory().getScopeStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() - .map(scope -> toRepresentation(scope, this.authorization, finalDeep)) + .map(scope -> toRepresentation(scope)) .collect(Collectors.toList())) .build(); } + + private void audit(@Context UriInfo uriInfo, ScopeRepresentation resource, OperationType operation) { + audit(uriInfo, resource, null, operation); + } + + private void audit(@Context UriInfo uriInfo, ScopeRepresentation resource, String id, OperationType operation) { + if (authorization.getRealm().isAdminEventsEnabled()) { + if (id != null) { + adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success(); + } else { + adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success(); + } + } + } } diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java index 45fb6fe8f8..377927983e 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java @@ -27,12 +27,17 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.protection.permission.PermissionService; import org.keycloak.authorization.protection.permission.PermissionsService; import org.keycloak.authorization.protection.resource.ResourceService; +import org.keycloak.common.ClientConnection; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.resources.admin.AdminAuth; +import org.keycloak.services.resources.admin.AdminEventBuilder; import javax.ws.rs.Path; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response.Status; /** @@ -41,6 +46,8 @@ import javax.ws.rs.core.Response.Status; public class ProtectionService { private final AuthorizationProvider authorization; + @Context + protected ClientConnection clientConnection; public ProtectionService(AuthorizationProvider authorization) { this.authorization = authorization; @@ -50,7 +57,12 @@ public class ProtectionService { public Object resource() { KeycloakIdentity identity = createIdentity(); ResourceServer resourceServer = getResourceServer(identity); - ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null); + RealmModel realm = authorization.getRealm(); + ClientModel client = realm.getClientById(identity.getId()); + KeycloakSession keycloakSession = authorization.getKeycloakSession(); + UserModel serviceAccount = keycloakSession.users().getServiceAccount(client); + AdminEventBuilder adminEvent = new AdminEventBuilder(realm, new AdminAuth(realm, identity.getAccessToken(), serviceAccount, client), keycloakSession, clientConnection); + ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null, adminEvent.realm(realm).authClient(client).authUser(serviceAccount)); ResteasyProviderFactory.getInstance().injectProperties(resourceManager); diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java index 1695ba53ca..c2e11dce03 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java @@ -31,8 +31,10 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.admin.ResourceSetService; @@ -68,10 +70,10 @@ public class ResourceService { @POST @Consumes("application/json") @Produces("application/json") - public Response create(UmaResourceRepresentation umaResource) { + public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation umaResource) { checkResourceServerSettings(); ResourceRepresentation resource = toResourceRepresentation(umaResource); - Response response = this.resourceManager.create(resource); + Response response = this.resourceManager.create(uriInfo, resource); if (response.getEntity() instanceof ResourceRepresentation) { return Response.status(Status.CREATED).entity(toUmaRepresentation((ResourceRepresentation) response.getEntity())).build(); @@ -84,9 +86,9 @@ public class ResourceService { @PUT @Consumes("application/json") @Produces("application/json") - public Response update(@PathParam("id") String id, UmaResourceRepresentation representation) { + public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, UmaResourceRepresentation representation) { ResourceRepresentation resource = toResourceRepresentation(representation); - Response response = this.resourceManager.update(id, resource); + Response response = this.resourceManager.update(uriInfo, id, resource); if (response.getEntity() instanceof ResourceRepresentation) { return Response.noContent().build(); @@ -97,9 +99,9 @@ public class ResourceService { @Path("/{id}") @DELETE - public Response delete(@PathParam("id") String id) { + public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) { checkResourceServerSettings(); - return this.resourceManager.delete(id); + return this.resourceManager.delete(uriInfo, id); } @Path("/{id}") diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 8f2e153956..facce6ca2d 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -343,7 +343,7 @@ public class ExportUtils { representation.setPolicies(policies); List scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel.getId()).stream().map(scope -> { - ScopeRepresentation rep = toRepresentation(scope, authorization); + ScopeRepresentation rep = toRepresentation(scope); rep.setId(null); rep.setPolicies(null); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java index dfae5653f9..4fc73f7b20 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java @@ -151,7 +151,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide } private void configureAuthorizationSettings(KeycloakSession session, ClientModel client, ClientManager.InstallationAdapterConfig rep) { - if (new AuthorizationService(session, client, null).isEnabled()) { + if (new AuthorizationService(session, client, null, null).isEnabled()) { PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig(); enforcerConfig.setEnforcementMode(null); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index a92729e2b4..43c897c374 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -148,36 +148,13 @@ public class ClientResource { try { updateClientFromRep(rep, client, session); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); + updateAuthorizationSettings(rep); return Response.noContent().build(); } catch (ModelDuplicateException e) { return ErrorResponse.exists("Client " + rep.getClientId() + " already exists"); } } - public void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException { - if (TRUE.equals(rep.isServiceAccountsEnabled())) { - UserModel serviceAccount = this.session.users().getServiceAccount(client); - - if (serviceAccount == null) { - new ClientManager(new RealmManager(session)).enableServiceAccount(client); - } - } - - if (!rep.getClientId().equals(client.getClientId())) { - new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId()); - } - - RepresentationToModel.updateClient(rep, client); - - if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { - if (TRUE.equals(rep.getAuthorizationServicesEnabled())) { - authorization().enable(); - } else { - authorization().disable(); - } - } - } - /** * Get representation of the client * @@ -587,10 +564,36 @@ public class ClientResource { public AuthorizationService authorization() { ProfileHelper.requireFeature(Profile.Feature.AUTHORIZATION); - AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth); + AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; } + + private void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException { + if (TRUE.equals(rep.isServiceAccountsEnabled())) { + UserModel serviceAccount = this.session.users().getServiceAccount(client); + + if (serviceAccount == null) { + new ClientManager(new RealmManager(session)).enableServiceAccount(client); + } + } + + if (!rep.getClientId().equals(client.getClientId())) { + new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId()); + } + + RepresentationToModel.updateClient(rep, client); + } + + private void updateAuthorizationSettings(ClientRepresentation rep) { + if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { + if (TRUE.equals(rep.getAuthorizationServicesEnabled())) { + authorization().enable(false); + } else { + authorization().disable(); + } + } + } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 3fa3c75158..a488ba3497 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -129,7 +129,7 @@ public class ClientsResource { } private AuthorizationService getAuthorizationService(ClientModel clientModel) { - return new AuthorizationService(session, clientModel, auth); + return new AuthorizationService(session, clientModel, auth, adminEvent); } /** @@ -167,14 +167,14 @@ public class ClientsResource { } } + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success(); + if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { if (TRUE.equals(rep.getAuthorizationServicesEnabled())) { - getAuthorizationService(clientModel).enable(); + getAuthorizationService(clientModel).enable(true); } } - adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success(); - return Response.created(uriInfo.getAbsolutePathBuilder().path(clientModel.getId()).build()).build(); } catch (ModelDuplicateException e) { return ErrorResponse.exists("Client " + rep.getClientId() + " already exists"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java index 244323b0d9..ae122f6d79 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java @@ -107,7 +107,9 @@ public abstract class AbstractClientTest extends AbstractAuthTest { clientRep.setPublicClient(Boolean.FALSE); clientRep.setAuthorizationServicesEnabled(Boolean.TRUE); clientRep.setServiceAccountsEnabled(Boolean.TRUE); - return createClient(clientRep); + String id = createClient(clientRep); + assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientResourcePath(id), ResourceType.AUTHORIZATION_RESOURCE_SERVER); + return id; } protected ClientRepresentation createOidcClientRep(String name) {