From b56e23ededaaac749ccfc2d6d7e2e97f59ab6fbd Mon Sep 17 00:00:00 2001 From: Jonas Pettersson Date: Fri, 2 Dec 2016 16:48:47 +0100 Subject: [PATCH] KEYCLOAK-4018 Client-Based Policy --- .../provider/client/ClientPolicyProvider.java | 37 ++++++ .../client/ClientPolicyProviderFactory.java | 111 ++++++++++++++++++ ...tion.policy.provider.PolicyProviderFactory | 3 +- .../messages/admin-messages_en.properties | 5 + .../messages/admin-messages_no.properties | 5 + .../admin/resources/js/authz/authz-app.js | 22 ++++ .../resources/js/authz/authz-controller.js | 93 +++++++++++++++ .../resource-server-policy-client-detail.html | 91 ++++++++++++++ 8 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java create mode 100644 authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java create mode 100644 themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java new file mode 100644 index 0000000000..a23296a5a5 --- /dev/null +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java @@ -0,0 +1,37 @@ +package org.keycloak.authorization.policy.provider.client; + +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.policy.evaluation.Evaluation; +import org.keycloak.authorization.policy.evaluation.EvaluationContext; +import org.keycloak.authorization.policy.provider.PolicyProvider; + +import static org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory.getClients; + +public class ClientPolicyProvider implements PolicyProvider { + + private final Policy policy; + + public ClientPolicyProvider(Policy policy) { + this.policy = policy; + } + + @Override + public void evaluate(Evaluation evaluation) { + EvaluationContext context = evaluation.getContext(); + String[] clientIds = getClients(this.policy); + + if (clientIds.length > 0) { + for (String clientId : clientIds) { + if (context.getIdentity().getId().equals(clientId)) { + evaluation.grant(); + return; + } + } + } + } + + @Override + public void close() { + + } +} diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java new file mode 100644 index 0000000000..9cf33487fe --- /dev/null +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java @@ -0,0 +1,111 @@ +package org.keycloak.authorization.policy.provider.client; + +import org.keycloak.Config; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.policy.provider.PolicyProvider; +import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel.ClientRemovedEvent; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ClientPolicyProviderFactory implements PolicyProviderFactory { + + @Override + public String getName() { + return "Client"; + } + + @Override + public String getGroup() { + return "Identity Based"; + } + + @Override + public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { + return new ClientPolicyProvider(policy); + } + + @Override + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + return null; + } + + @Override + public PolicyProvider create(KeycloakSession session) { + return null; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + factory.register(event -> { + if (event instanceof ClientRemovedEvent) { + KeycloakSession keycloakSession = ((ClientRemovedEvent) event).getKeycloakSession(); + AuthorizationProvider provider = keycloakSession.getProvider(AuthorizationProvider.class); + PolicyStore policyStore = provider.getStoreFactory().getPolicyStore(); + ClientModel removedClient = ((ClientRemovedEvent) event).getClient(); + + policyStore.findByType(getId()).forEach(policy -> { + List clients = new ArrayList<>(); + + for (String clientId : getClients(policy)) { + if (!clientId.equals(removedClient.getId())) { + clients.add(clientId); + } + } + + try { + if (clients.isEmpty()) { + policyStore.findDependentPolicies(policy.getId()).forEach(dependentPolicy -> { + dependentPolicy.removeAssociatedPolicy(policy); + }); + policyStore.delete(policy.getId()); + } else { + policy.getConfig().put("clients", JsonSerialization.writeValueAsString(clients)); + } + } catch (IOException e) { + throw new RuntimeException("Error while synchronizing clients with policy [" + policy.getName() + "].", e); + } + }); + } + }); + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return "client"; + } + + static String[] getClients(Policy policy) { + String clients = policy.getConfig().get("clients"); + + if (clients != null) { + try { + return JsonSerialization.readValue(clients.getBytes(), String[].class); + } catch (IOException e) { + throw new RuntimeException("Could not parse clients [" + clients + "] from policy config [" + policy.getName() + "].", e); + } + } + + return new String[]{}; + } +} diff --git a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory index 1e8dfd20cb..e4588f87a6 100644 --- a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory +++ b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory @@ -40,4 +40,5 @@ org.keycloak.authorization.policy.provider.resource.ResourcePolicyProviderFactor org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory org.keycloak.authorization.policy.provider.scope.ScopePolicyProviderFactory org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory -org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory \ No newline at end of file +org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory +org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 8cf09f3b7a..173bcfbd91 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1101,6 +1101,11 @@ authz-add-user-policy=Add User Policy authz-no-users-assigned=No users assigned. authz-policy-user-users.tooltip=Specifies which user(s) are allowed by this policy. +# Authz Client Policy Detail +authz-add-client-policy=Add Client Policy +authz-no-clients-assigned=No clients assigned. +authz-policy-client-clients.tooltip=Specifies which client(s) are allowed by this policy. + # Authz Time Policy Detail authz-add-time-policy=Add Time Policy authz-policy-time-not-before.tooltip=Defines the time before which the policy MUST NOT be granted. Only granted if current date/time is after or equal to this value. diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties index 9b8a6cb28f..da1127f358 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties @@ -1047,6 +1047,11 @@ authz-add-user-policy=Legg til policy for bruker authz-no-users-assigned=Ingen tildelte brukere. authz-policy-user-users.tooltip=Spesifiser bruker(e) som tillates av denne policien. + # Authz Client Policy Detail +authz-add-client-policy=Legg til policy for klient +authz-no-clients-assigned=Ingen tildelte klienter. +authz-policy-client-clients.tooltip=Spesifiser klient(er) som tillates av denne policien. + # Authz Time Policy Detail authz-add-time-policy=Legg til policy for tid authz-policy-time-not-before.tooltip=Definerer tiden f\u00F8r policien M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien. diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js index 467038c95c..f201fdccff 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js @@ -263,6 +263,28 @@ module.config(['$routeProvider', function ($routeProvider) { } }, controller: 'ResourceServerPolicyUserDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/client/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-client-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyClientDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/client/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-client-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyClientDetailCtrl' }).when('/realms/:realm/clients/:client/authz/resource-server/policy/role/create', { templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-role-detail.html', resolve: { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index 72b6c43c06..ea039c195a 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -883,6 +883,99 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route, }, realm, client, $scope); }); +module.controller('ResourceServerPolicyClientDetailCtrl', function($scope, $route, realm, client, PolicyController, Client) { + PolicyController.onInit({ + getPolicyType : function() { + return "client"; + }, + + onInit : function() { + $scope.clientsUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + Client.query({realm: $route.current.params.realm, search: query.term.trim(), max: 20}, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + return object.clientId; + } + }; + + $scope.selectedClients = []; + + $scope.selectClient = function(client) { + if (!client || !client.id) { + return; + } + + $scope.selectedClient = null; + + for (var i = 0; i < $scope.selectedClients.length; i++) { + if ($scope.selectedClients[i].id == client.id) { + return; + } + } + + $scope.selectedClients.push(client); + } + + $scope.removeFromList = function(list, index) { + list.splice(index, 1); + } + }, + + onInitUpdate : function(policy) { + var selectedClients = []; + + if (policy.config.clients) { + var clients = eval(policy.config.clients); + + for (var i = 0; i < clients.length; i++) { + Client.get({realm: $route.current.params.realm, client: clients[i]}, function(data) { + selectedClients.push(data); + $scope.selectedClients = angular.copy(selectedClients); + }); + } + } + + $scope.$watch('selectedClients', function() { + if (!angular.equals($scope.selectedClients, selectedClients)) { + $scope.changed = true; + } + }, true); + }, + + onUpdate : function() { + var clients = []; + + for (var i = 0; i < $scope.selectedClients.length; i++) { + clients.push($scope.selectedClients[i].id); + } + + $scope.policy.config.clients = JSON.stringify(clients); + }, + + onCreate : function() { + var clients = []; + + for (var i = 0; i < $scope.selectedClients.length; i++) { + clients.push($scope.selectedClients[i].id); + } + + $scope.policy.config.clients = JSON.stringify(clients); + } + }, realm, client, $scope); +}); + module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, Client, ClientRole, PolicyController, Role, RoleById) { PolicyController.onInit({ getPolicyType : function() { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html new file mode 100644 index 0000000000..634b836913 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html @@ -0,0 +1,91 @@ +
+ + + +

{{:: 'authz-add-client-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ + +
+ + +
+ {{:: 'authz-policy-client-clients.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + +
{{:: 'clientId' | translate}}{{:: 'actions' | translate}}
{{client.clientId}} + +
{{:: 'authz-no-clients-assigned' | translate}}
+
+
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file