Merge pull request #3597 from jlpettersson/KEYCLOAK-4018
KEYCLOAK-4018 Client-Based Policy
This commit is contained in:
commit
588e1711dd
10 changed files with 488 additions and 1 deletions
|
@ -0,0 +1,47 @@
|
|||
package org.keycloak.authorization.policy.provider.client;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
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 org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import static org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory.getClients;
|
||||
|
||||
public class ClientPolicyProvider implements PolicyProvider {
|
||||
|
||||
private final Policy policy;
|
||||
private final AuthorizationProvider authorization;
|
||||
|
||||
public ClientPolicyProvider(Policy policy, AuthorizationProvider authorization) {
|
||||
this.policy = policy;
|
||||
this.authorization = authorization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(Evaluation evaluation) {
|
||||
EvaluationContext context = evaluation.getContext();
|
||||
String[] clients = getClients(this.policy);
|
||||
|
||||
if (clients.length > 0) {
|
||||
for (String client : clients) {
|
||||
ClientModel clientModel = getCurrentRealm().getClientById(client);
|
||||
if (context.getAttributes().containsValue("kc.client.id", clientModel.getClientId())) {
|
||||
evaluation.grant();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
private RealmModel getCurrentRealm() {
|
||||
return this.authorization.getKeycloakSession().getContext().getRealm();
|
||||
}
|
||||
}
|
|
@ -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, authorization);
|
||||
}
|
||||
|
||||
@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[]{};
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -25,6 +25,10 @@ import org.keycloak.authorization.model.Policy;
|
|||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.authorization.store.StoreFactory;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.authorization.DecisionStrategy;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
@ -34,6 +38,7 @@ import javax.ws.rs.client.Entity;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -327,6 +332,93 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest {
|
|||
assertEquals(0, evaluationsUserRole.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceAccessWithClientBasedPolicy() throws Exception {
|
||||
ClientModel testClient1 = getClientByClientId("test-client-1");
|
||||
ClientModel testClient2 = getClientByClientId("test-client-2");
|
||||
Policy clientPolicy = createClientPolicy(Collections.singletonList(testClient1));
|
||||
|
||||
PolicyRepresentation newPermission = new PolicyRepresentation();
|
||||
|
||||
newPermission.setName("Client Permission");
|
||||
newPermission.setType("resource");
|
||||
|
||||
HashedMap config = new HashedMap();
|
||||
|
||||
config.put("defaultResourceType", "http://photoz.com/admin");
|
||||
config.put("applyPolicies", JsonSerialization.writeValueAsString(new String[] {clientPolicy.getId()}));
|
||||
|
||||
newPermission.setConfig(config);
|
||||
|
||||
Response response = newPermissionRequest().post(Entity.entity(newPermission, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
|
||||
|
||||
PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class);
|
||||
|
||||
onAuthorizationSession(authorizationProvider -> {
|
||||
Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId());
|
||||
|
||||
assertNotNull(policyModel);
|
||||
assertEquals(permission.getId(), policyModel.getId());
|
||||
assertEquals(newPermission.getName(), policyModel.getName());
|
||||
assertEquals(resourceServer.getId(), policyModel.getResourceServer().getId());
|
||||
});
|
||||
|
||||
Map<String, DefaultEvaluation> evaluations = performEvaluation(
|
||||
Collections.singletonList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
|
||||
createAccessTokenForClient(testClient1),
|
||||
createClientConnection("127.0.0.1"));
|
||||
|
||||
assertEquals(1, evaluations.size());
|
||||
assertTrue(evaluations.containsKey(clientPolicy.getId()));
|
||||
assertEquals(Effect.PERMIT, evaluations.get(clientPolicy.getId()).getEffect());
|
||||
|
||||
Map<String, DefaultEvaluation> evaluations2 = performEvaluation(
|
||||
Collections.singletonList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
|
||||
createAccessTokenForClient(testClient2),
|
||||
createClientConnection("127.0.0.1"));
|
||||
|
||||
assertEquals(1, evaluations2.size());
|
||||
assertTrue(evaluations2.containsKey(clientPolicy.getId()));
|
||||
assertEquals(Effect.DENY, evaluations2.get(clientPolicy.getId()).getEffect());
|
||||
}
|
||||
|
||||
private Policy createClientPolicy(List<ClientModel> allowedClients) {
|
||||
return onAuthorizationSession(authorizationProvider -> {
|
||||
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
Policy policy = policyStore.create("Client-Based Policy", "client", resourceServer);
|
||||
|
||||
List<String> clientIds = new ArrayList<>();
|
||||
for (ClientModel client : allowedClients) {
|
||||
clientIds.add(client.getId());
|
||||
}
|
||||
|
||||
String[] clients = clientIds.toArray(new String[clientIds.size()]);
|
||||
HashedMap config = new HashedMap();
|
||||
|
||||
try {
|
||||
config.put("clients", JsonSerialization.writeValueAsString(clients));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
policy.setConfig(config);
|
||||
|
||||
return policy;
|
||||
});
|
||||
}
|
||||
|
||||
private AccessToken createAccessTokenForClient(ClientModel client) {
|
||||
AccessToken accessToken = new AccessToken();
|
||||
|
||||
accessToken.setRealmAccess(new AccessToken.Access());
|
||||
accessToken.issuedFor = client.getClientId();
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
private PolicyRepresentation createAlbumResourceTypePermission() throws Exception {
|
||||
PolicyRepresentation newPermission = new PolicyRepresentation();
|
||||
|
||||
|
|
|
@ -153,6 +153,26 @@
|
|||
"/confidential-no-service-account/*"
|
||||
],
|
||||
"secret": "secret"
|
||||
},
|
||||
{
|
||||
"clientId": "test-client-1",
|
||||
"secret": "secret",
|
||||
"enabled": true,
|
||||
"baseUrl": "test-client-1",
|
||||
"redirectUris": [
|
||||
"/test-client-1/*"
|
||||
],
|
||||
"webOrigins" : ["*"]
|
||||
},
|
||||
{
|
||||
"clientId": "test-client-2",
|
||||
"secret": "secret",
|
||||
"enabled": true,
|
||||
"baseUrl": "test-client-2",
|
||||
"redirectUris": [
|
||||
"/test-client-2/*"
|
||||
],
|
||||
"webOrigins" : ["*"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue