KEYCLOAK-4018 Client-Based Policy

This commit is contained in:
Jonas Pettersson 2016-12-02 16:48:47 +01:00
parent 8842d88058
commit b56e23eded
No known key found for this signature in database
GPG key ID: 1D3D970772EA56C9
8 changed files with 366 additions and 1 deletions

View file

@ -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() {
}
}

View file

@ -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<String> 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[]{};
}
}

View file

@ -41,3 +41,4 @@ 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
org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory

View file

@ -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.

View file

@ -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.

View file

@ -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: {

View file

@ -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() {

View file

@ -0,0 +1,91 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy">{{:: 'authz-policies' | translate}}</a></li>
<li data-ng-show="create">{{:: 'authz-add-client-policy' | translate}}</li>
<li data-ng-hide="create">{{:: 'client' | translate}}</li>
<li data-ng-hide="create">{{originalPolicy.name}}</li>
</ol>
<h1 data-ng-show="create">{{:: 'authz-add-client-policy' | translate}}</h1>
<h1 data-ng-hide="create">{{originalPolicy.name|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!create"
data-ng-click="remove()"></i></h1>
<form class="form-horizontal" name="clientForm" novalidate>
<fieldset class="border-top">
<div class="form-group">
<label class="col-md-2 control-label" for="name">{{:: 'name' | translate}} <span class="required">*</span></label>
<div class="col-sm-6">
<input class="form-control" type="text" id="name" name="name" data-ng-model="policy.name" autofocus required data-ng-blur="checkNewNameAvailability()">
</div>
<kc-tooltip>{{:: 'authz-policy-name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
<div class="col-sm-6">
<input class="form-control" type="text" id="description" name="description" data-ng-model="policy.description">
</div>
<kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clients">{{:: 'clients' | translate}} <span class="required">*</span></label>
<div class="col-md-6">
<input type="hidden" ui-select2="clientsUiSelect" id="clients" data-ng-model="selectedClient" data-ng-change="selectClient(selectedClient);" data-placeholder="Select an client..." data-ng-required="selectedClients.length == 0">
</input>
</div>
<kc-tooltip>{{:: 'authz-policy-client-clients.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" style="margin-top: -15px;">
<label class="col-md-2 control-label"></label>
<div class="col-sm-3">
<table class="table table-striped table-bordered">
<thead>
<tr data-ng-hide="!selectedClients.length">
<th>{{:: 'clientId' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="client in selectedClients | orderBy:'clientId'">
<td>{{client.clientId}}</td>
<td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedClients, $index);">{{:: 'remove' | translate}}</button>
</td>
</tr>
<tr data-ng-show="!selectedClients.length">
<td class="text-muted" colspan="3">{{:: 'authz-no-clients-assigned' | translate}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="policy.logic">{{:: 'authz-policy-logic' | translate}}</label>
<div class="col-sm-1">
<select class="form-control" id="policy.logic"
data-ng-model="policy.logic">
<option value="POSITIVE">{{:: 'authz-policy-logic-positive' | translate}}</option>
<option value="NEGATIVE">{{:: 'authz-policy-logic-negative' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'authz-policy-logic.tooltip' | translate}}</kc-tooltip>
</div>
<input type="hidden" data-ng-model="policy.type"/>
</fieldset>
<div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
</div>
</div>
</form>
</div>
<kc-menu></kc-menu>