[KEYCLOAK-3338] - Adding client roles to role policy and UX improvements

This commit is contained in:
Pedro Igor 2016-07-27 15:15:14 -03:00
parent 3973aed57d
commit 7e1b97888a
43 changed files with 830 additions and 417 deletions

View file

@ -23,9 +23,12 @@ 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 org.keycloak.models.RoleModel;
import java.util.Map;
import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles;
/**
@ -47,22 +50,40 @@ public class RolePolicyProvider implements PolicyProvider {
@Override
public void evaluate(Evaluation evaluation) {
EvaluationContext context = evaluation.getContext();
String[] roleIds = getRoles(this.policy);
Map<String, Object>[] roleIds = getRoles(this.policy);
if (roleIds.length > 0) {
Identity identity = context.getIdentity();
Identity identity = evaluation.getContext().getIdentity();
for (String roleId : roleIds) {
RoleModel role = getCurrentRealm().getRoleById(roleId);
for (Map<String, Object> current : roleIds) {
RoleModel role = getCurrentRealm().getRoleById((String) current.get("id"));
if (role != null && identity.hasRole(role.getName())) {
if (role != null) {
boolean hasRole = hasRole(identity, role);
if (!hasRole && Boolean.valueOf(isRequired(current))) {
evaluation.deny();
return;
} else if (hasRole) {
evaluation.grant();
break;
}
}
}
}
}
private boolean isRequired(Map<String, Object> current) {
return (boolean) current.getOrDefault("required", false);
}
private boolean hasRole(Identity identity, RoleModel role) {
String roleName = role.getName();
if (role.isClientRole()) {
ClientModel clientModel = getCurrentRealm().getClientById(role.getContainerId());
return identity.hasClientRole(clientModel.getClientId(), roleName);
}
return identity.hasRealmRole(roleName);
}
private RealmModel getCurrentRealm() {
return this.authorization.getKeycloakSession().getContext().getRealm();

View file

@ -34,7 +34,9 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -81,11 +83,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
RoleModel removedRole = ((RoleRemovedEvent) event).getRole();
policyStore.findByType(getId()).forEach(policy -> {
List<String> roles = new ArrayList<>();
List<Map> roles = new ArrayList<>();
for (String roleId : getRoles(policy)) {
if (!roleId.equals(removedRole.getId())) {
roles.add(roleId);
for (Map<String,Object> role : getRoles(policy)) {
if (!role.get("id").equals(removedRole.getId())) {
Map updated = new HashMap();
updated.put("id", role.get("id"));
Object required = role.get("required");
if (required != null) {
updated.put("required", required);
}
roles.add(updated);
}
}
@ -96,7 +104,9 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
});
policyStore.delete(policy.getId());
} else {
policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
Map<String, String> config = policy.getConfig();
config.put("roles", JsonSerialization.writeValueAsString(roles));
policy.setConfig(config);
}
} catch (IOException e) {
throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e);
@ -116,17 +126,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
return "role";
}
static String[] getRoles(Policy policy) {
static Map<String, Object>[] getRoles(Policy policy) {
String roles = policy.getConfig().get("roles");
if (roles != null) {
try {
return JsonSerialization.readValue(roles.getBytes(), String[].class);
return JsonSerialization.readValue(roles.getBytes(), Map[].class);
} catch (IOException e) {
throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e);
}
}
return new String[]{};
return new Map[] {};
}
}

View file

@ -32,6 +32,8 @@ public class RoleRepresentation {
protected Boolean scopeParamRequired;
protected boolean composite;
protected Composites composites;
private Boolean clientRole;
private String containerId;
public static class Composites {
protected Set<String> realm;
@ -122,4 +124,20 @@ public class RoleRepresentation {
public void setComposite(boolean composite) {
this.composite = composite;
}
public Boolean getClientRole() {
return clientRole;
}
public void setClientRole(Boolean clientRole) {
this.clientRole = clientRole;
}
public String getContainerId() {
return containerId;
}
public void setContainerId(String containerId) {
this.containerId = containerId;
}
}

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.representations.idm.authorization;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URI;
@ -39,12 +40,14 @@ public class ResourceRepresentation {
private String name;
private String uri;
private String type;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<ScopeRepresentation> scopes;
@JsonProperty("icon_uri")
private String iconUri;
private ResourceOwnerRepresentation owner;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PolicyRepresentation> policies;
/**

View file

@ -34,6 +34,7 @@ public class ScopeRepresentation {
private String name;
private String iconUri;
private List<PolicyRepresentation> policies;
private List<ResourceRepresentation> resources;
/**
* Creates an instance.
@ -94,6 +95,14 @@ public class ScopeRepresentation {
this.policies = policies;
}
public List<ResourceRepresentation> getResources() {
return this.resources;
}
public void setResources(List<ResourceRepresentation> resources) {
this.resources = resources;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

View file

@ -1,4 +1,6 @@
{
"allowRemoteResourceManagement": false,
"policyEnforcementMode": "ENFORCING",
"resources": [
{
"name": "Default Resource",
@ -8,22 +10,26 @@
],
"policies": [
{
"name": "Only From Realm Policy",
"name": "Default Policy",
"description": "A policy that grants access only for users within this realm",
"type": "js",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"applyPolicies": "[]",
"code": "var context = $evaluation.getContext();\n\n// using attributes from the evaluation context to obtain the realm\nvar contextAttributes = context.getAttributes();\nvar realmName = contextAttributes.getValue('kc.realm.name').asString(0);\n\n// using attributes from the identity to obtain the issuer\nvar identity = context.getIdentity();\nvar identityAttributes = identity.getAttributes();\nvar issuer = identityAttributes.getValue('iss').asString(0);\n\n// only users from the realm have access granted \nif (issuer.endsWith(realmName)) {\n $evaluation.grant();\n}"
"code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
}
},
{
"name": "Default Permission",
"description": "A permission that applies to the default resource type",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"defaultResourceType": "urn:hello-world-authz-service:resources:default",
"default": "true",
"applyPolicies": "[\"Only From Realm Policy\"]"
"applyPolicies": "[\"Default Policy\"]"
}
}
]

View file

@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl">
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
<div id="content-area" class="col-md-9" role="main">
<div id="content" ng-view/>

View file

@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
}
$scope.Identity = Identity;
});
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
$scope.profile = Profile.get();
});
module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
$scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data;
});
$scope.deleteAlbum = function (album) {
var newAlbum = new Album(album);
newAlbum.$delete({id: album.id}, function () {
new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}

View file

@ -10,7 +10,7 @@
<td>
<ul>
<li data-ng-repeat="p in value">
<a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
<a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
</li>
</ul>
</td>

View file

@ -1,4 +1,4 @@
<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/>
<br/>
@ -15,7 +15,7 @@
</thead>
<tbody>
<tr data-ng-repeat="p in albums">
<td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
<td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
</tr>
</tbody>
</table>

View file

@ -53,7 +53,7 @@
}
],
"realmRoles": [
"user", "admin", "uma_authorization"
"admin", "uma_authorization"
],
"clientRoles": {
"realm-management": [

View file

@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view"
},
{
"name": "urn:photoz.com:scopes:album:create"
"name": "urn:photoz.com:scopes:album:delete"
},
{
"name": "urn:photoz.com:scopes:album:delete"
"name": "urn:photoz.com:scopes:album:create"
}
]
},
@ -44,12 +44,15 @@
"name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something",
"type": "drools",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession",
"mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy",
"applyPolicies": "[]",
"scannerPeriod": "1",
"scannerPeriodUnit": "Hours"
}
@ -58,16 +61,22 @@
"name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something",
"type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"roles": "[\"admin\"]"
"applyPolicies": "[]",
"roles": "[{\"id\":\"admin\",\"required\":true}]"
}
},
{
"name": "Any User Policy",
"description": "Defines that any user can do something",
"type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"roles": "[\"user\"]"
"applyPolicies": "[]",
"roles": "[{\"id\":\"user\"}]"
}
},
{
@ -77,6 +86,7 @@
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n $evaluation.grant();\n}"
}
},
@ -84,6 +94,8 @@
"name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
}
@ -92,35 +104,28 @@
"name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
"applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
}
},
{
"name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org",
"type": "js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
}
},
{
"name": "Only in the Period",
"description": "Access granted only during the morning",
"type": "time",
"config": {
"noa": "2016-01-03 23:59:59",
"expirationUnit": "Minutes",
"nbf": "2016-01-01 00:00:00",
"expirationTime": "1"
}
},
{
"name": "Album Resource Permission",
"description": "General policies that apply to all album resources.",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"defaultResourceType": "http://photoz.com/album",

View file

@ -109,13 +109,7 @@ public class AlbumService {
private void createProtectedResource(Album album) {
try {
HashSet<ScopeRepresentation> scopes = new HashSet<>();
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_CREATE));
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
albumResource.setOwner(album.getUserId());

View file

@ -38,6 +38,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -99,7 +100,7 @@ public class CachedPolicyStore implements PolicyStore {
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
return getDelegate().findByResourceServer(resourceServerId);
return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
@ -179,12 +180,12 @@ public class CachedPolicyStore implements PolicyStore {
@Override
public List<Policy> findByType(String type) {
return getDelegate().findByType(type);
return getDelegate().findByType(type).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
public List<Policy> findDependentPolicies(String id) {
return getDelegate().findDependentPolicies(id);
return getDelegate().findDependentPolicies(id).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
private String getCacheKeyForPolicy(String policyId) {

View file

@ -68,7 +68,21 @@ public class CachedResourceStore implements ResourceStore {
@Override
public void delete(String id) {
this.cache.remove(getCacheKeyForResource(id));
List<CachedResource> removed = this.cache.remove(getCacheKeyForResource(id));
if (removed != null) {
CachedResource cachedResource = removed.get(0);
List<String> byOwner = this.cache.get(getResourceOwnerCacheKey(cachedResource.getOwner()));
if (byOwner != null) {
byOwner.remove(id);
if (byOwner.isEmpty()) {
this.cache.remove(getResourceOwnerCacheKey(cachedResource.getOwner()));
}
}
}
getDelegate().delete(id);
}
@ -109,12 +123,12 @@ public class CachedResourceStore implements ResourceStore {
@Override
public List<Resource> findByResourceServer(String resourceServerId) {
return getDelegate().findByResourceServer(resourceServerId);
return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
}
@Override
public List<Resource> findByScope(String... id) {
return getDelegate().findByScope(id);
return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
}
@Override
@ -283,7 +297,7 @@ public class CachedResourceStore implements ResourceStore {
return;
}
cached = new ArrayList<>();
this.cache.put(getResourceOwnerCacheKey(resource.getOwner()), cached);
this.cache.put(cacheKey, cached);
}
if (cached != null && !cached.contains(resource.getId())) {

View file

@ -98,7 +98,7 @@ public class JPAResourceStore implements ResourceStore {
@Override
public List<Resource> findByScope(String... id) {
Query query = entityManager.createQuery("from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
query.setParameter("scopeIds", Arrays.asList(id));

View file

@ -95,7 +95,7 @@ public interface Attributes {
private final String[] values;
private final String name;
Entry(String name, Collection<String> values) {
public Entry(String name, Collection<String> values) {
this.name = name;
this.values = values.toArray(new String[values.size()]);
}

View file

@ -19,6 +19,10 @@ package org.keycloak.authorization.identity;
import org.keycloak.authorization.attribute.Attributes;
import java.util.Collection;
import java.util.Map;
import java.util.function.Predicate;
/**
* <p>Represents a security identity, which can be a person or non-person entity that was previously authenticated.
*
@ -45,13 +49,53 @@ public interface Identity {
Attributes getAttributes();
/**
* Indicates if this identity is granted with a role with the given <code>roleName</code>.
* Indicates if this identity is granted with a role (realm or client) with the given <code>roleName</code>.
*
* @param roleName the name of the role
*
* @return true if the identity has the given role. Otherwise, it returns false.
*/
default boolean hasRole(String roleName) {
return getAttributes().containsValue("roles", roleName);
return hasRealmRole(roleName) || hasClientRole(roleName);
}
/**
* Indicates if this identity is granted with a realm role with the given <code>roleName</code>.
*
* @param roleName the name of the role
*
* @return true if the identity has the given role. Otherwise, it returns false.
*/
default boolean hasRealmRole(String roleName) {
return getAttributes().containsValue("kc.realm.roles", roleName);
}
/**
* Indicates if this identity is granted with a client role with the given <code>roleName</code>.
*
* @param clientId the client id
* @param roleName the name of the role
*
* @return true if the identity has the given role. Otherwise, it returns false.
*/
default boolean hasClientRole(String clientId, String roleName) {
return getAttributes().containsValue("kc.client." + clientId + ".roles", roleName);
}
/**
* Indicates if this identity is granted with a client role with the given <code>roleName</code>.
*
* @param roleName the name of the role
*
* @return true if the identity has the given role. Otherwise, it returns false.
*/
default boolean hasClientRole(String roleName) {
return getAttributes().toMap().entrySet().stream().filter(entry -> {
String key = entry.getKey();
if (key.startsWith("kc.client") && key.endsWith(".roles")) {
return getAttributes().containsValue(key, roleName);
}
return false;
}).findFirst().isPresent();
}
}

View file

@ -17,11 +17,22 @@
package org.keycloak.migration.migrators;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.util.JsonSerialization;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
@ -33,6 +44,7 @@ public class MigrateTo2_1_0 {
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateDefaultRequiredAction(realm);
migrateRolePolicies(realm, session);
}
}
@ -46,4 +58,46 @@ public class MigrateTo2_1_0 {
otpAction.setName("Configure OTP");
}
// KEYCLOAK-3338: Changes to how role policy config is stored"
private void migrateRolePolicies(RealmModel realm, KeycloakSession session) {
AuthorizationProvider authorizationProvider = session.getProvider(AuthorizationProvider.class);
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
PolicyStore policyStore = storeFactory.getPolicyStore();
realm.getClients().forEach(clientModel -> {
ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getId());
if (resourceServer != null) {
policyStore.findByType("role").forEach(policy -> {
Map<String, String> config = policy.getConfig();
String roles = config.get("roles");
List roleConfig;
try {
roleConfig = JsonSerialization.readValue(roles, List.class);
} catch (Exception e) {
throw new RuntimeException("Malformed configuration for role policy [" + policy.getName() + "].", e);
}
if (!roleConfig.isEmpty() && roleConfig.get(0) instanceof String) {
try {
config.put("roles", JsonSerialization.writeValueAsString(roleConfig.stream().map(new Function<String, Map>() {
@Override
public Map apply(String roleId) {
Map updated = new HashMap();
updated.put("id", roleId);
return updated;
}
}).collect(Collectors.toList())));
policy.setConfig(config);
} catch (Exception e) {
throw new RuntimeException("Failed to migrate role policy [" + policy.getName() + "].", e);
}
}
});
}
});
}
}

View file

@ -218,6 +218,8 @@ public class ModelToRepresentation {
rep.setDescription(role.getDescription());
rep.setScopeParamRequired(role.isScopeParamRequired());
rep.setComposite(role.isComposite());
rep.setClientRole(role.isClientRole());
rep.setContainerId(role.getContainerId());
return rep;
}

View file

@ -136,11 +136,19 @@ public class PolicyEvaluationService {
}
private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) {
if (representation.isEntitlements()) {
List<PolicyEvaluationRequest.Resource> resources = representation.getResources();
for (PolicyEvaluationRequest.Resource resource : new ArrayList<>(resources)) {
if (resource.getId() == null && (resource.getScopes() == null || resource.getScopes().isEmpty())) {
resources.remove(resource);
}
}
if (representation.isEntitlements() || resources.isEmpty()) {
return Permissions.all(this.resourceServer, evaluationContext.getIdentity(), authorization);
}
return representation.getResources().stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
return resources.stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
Set<String> givenScopes = resource.getScopes();
if (givenScopes == null) {
@ -157,7 +165,13 @@ public class PolicyEvaluationService {
} else if (resource.getType() != null) {
return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer));
} else {
return scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer));
List<ResourcePermission> collect = scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList());
for (Scope scope : scopes) {
collect.addAll(storeFactory.getResourceStore().findByScope(scope.getId()).stream().map(resource12 -> new ResourcePermission(resource12, asList(scope), resourceServer)).collect(Collectors.toList()));
}
return collect.stream();
}
}).collect(Collectors.toList());
}

View file

@ -154,7 +154,11 @@ public class PolicyService {
}
policyStore.findDependentPolicies(id).forEach(dependentPolicy -> {
if (dependentPolicy.getAssociatedPolicies().size() == 1) {
policyStore.delete(dependentPolicy.getId());
} else {
dependentPolicy.removeAssociatedPolicy(policy);
}
});
policyStore.delete(policy.getId());
@ -271,7 +275,6 @@ public class PolicyService {
}
StoreFactory storeFactory = authorization.getStoreFactory();
PolicyStore policyStore = storeFactory.getPolicyStore();
for (String scopeId : scopeIds) {
boolean hasScope = false;

View file

@ -17,8 +17,6 @@
*/
package org.keycloak.authorization.admin;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.util.Models;
@ -146,7 +144,11 @@ public class ResourceServerService {
.stream().map(resource -> {
ResourceRepresentation rep = Models.toRepresentation(resource, resourceServer, authorization);
if (rep.getOwner().getId().equals(this.resourceServer.getClientId())) {
rep.setOwner(null);
} else {
rep.getOwner().setId(null);
}
rep.setId(null);
rep.setPolicies(null);
rep.getScopes().forEach(scopeRepresentation -> {
@ -175,15 +177,8 @@ public class ResourceServerService {
ScopeRepresentation rep = Models.toRepresentation(scope, authorization);
rep.setId(null);
rep.getPolicies().forEach(policyRepresentation -> {
policyRepresentation.setId(null);
policyRepresentation.setConfig(null);
policyRepresentation.setType(null);
policyRepresentation.setDecisionStrategy(null);
policyRepresentation.setDescription(null);
policyRepresentation.setDependentPolicies(null);
});
rep.setPolicies(null);
rep.setResources(null);
return rep;
}).collect(Collectors.toList());
@ -258,131 +253,74 @@ public class ResourceServerService {
String roles = config.get("roles");
if (roles != null && !roles.isEmpty()) {
roles = roles.replace("[", "");
roles = roles.replace("]", "");
if (!roles.isEmpty()) {
String roleNames = "";
for (String role : roles.split(",")) {
if (!roleNames.isEmpty()) {
roleNames = roleNames + ",";
}
role = role.replace("\"", "");
roleNames = roleNames + "\"" + this.realm.getRole(role).getId() + "\"";
}
config.put("roles", "[" + roleNames + "]");
try {
List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> {
roleConfig.put("id", realm.getRole(roleConfig.get("id").toString()).getId());
return roleConfig;
}).collect(Collectors.toList())));
} catch (Exception e) {
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String users = config.get("users");
if (users != null) {
users = users.replace("[", "");
users = users.replace("]", "");
if (!users.isEmpty()) {
String userNames = "";
for (String user : users.split(",")) {
if (!userNames.isEmpty()) {
userNames = userNames + ",";
}
user = user.replace("\"", "");
userNames = userNames + "\"" + this.session.users().getUserByUsername(user, this.realm).getId() + "\"";
}
config.put("users", "[" + userNames + "]");
if (users != null && !users.isEmpty()) {
try {
List<String> usersMap = JsonSerialization.readValue(users, List.class);
config.put("users", JsonSerialization.writeValueAsString(usersMap.stream().map(userName -> this.session.users().getUserByUsername(userName, this.realm).getId()).collect(Collectors.toList())));
} catch (Exception e) {
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String scopes = config.get("scopes");
if (scopes != null && !scopes.isEmpty()) {
scopes = scopes.replace("[", "");
scopes = scopes.replace("]", "");
if (!scopes.isEmpty()) {
String scopeNames = "";
for (String scope : scopes.split(",")) {
if (!scopeNames.isEmpty()) {
scopeNames = scopeNames + ",";
}
scope = scope.replace("\"", "");
Scope newScope = scopeStore.findByName(scope, resourceServer.getId());
try {
List<String> scopesMap = JsonSerialization.readValue(scopes, List.class);
config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> {
Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId());
if (newScope == null) {
throw new RuntimeException("Scope with name [" + scope + "] not defined.");
throw new RuntimeException("Scope with name [" + scopeName + "] not defined.");
}
scopeNames = scopeNames + "\"" + newScope.getId() + "\"";
}
config.put("scopes", "[" + scopeNames + "]");
return newScope.getId();
}).collect(Collectors.toList())));
} catch (Exception e) {
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String policyResources = config.get("resources");
if (policyResources != null && !policyResources.isEmpty()) {
policyResources = policyResources.replace("[", "");
policyResources = policyResources.replace("]", "");
if (!policyResources.isEmpty()) {
String resourceNames = "";
for (String resource : policyResources.split(",")) {
if (!resourceNames.isEmpty()) {
resourceNames = resourceNames + ",";
}
resource = resource.replace("\"", "");
if ("".equals(resource)) {
continue;
}
resourceNames = resourceNames + "\"" + storeFactory.getResourceStore().findByName(resource, resourceServer.getId()).getId() + "\"";
}
config.put("resources", "[" + resourceNames + "]");
try {
List<String> resources = JsonSerialization.readValue(policyResources, List.class);
config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> storeFactory.getResourceStore().findByName(resourceName, resourceServer.getId()).getId()).collect(Collectors.toList())));
} catch (Exception e) {
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String applyPolicies = config.get("applyPolicies");
if (applyPolicies != null && !applyPolicies.isEmpty()) {
applyPolicies = applyPolicies.replace("[", "");
applyPolicies = applyPolicies.replace("]", "");
if (!applyPolicies.isEmpty()) {
String policyNames = "";
for (String pId : applyPolicies.split(",")) {
if (!policyNames.isEmpty()) {
policyNames = policyNames + ",";
}
pId = pId.replace("\"", "").trim();
Policy policy = policyStore.findByName(pId, resourceServer.getId());
try {
List<String> policies = JsonSerialization.readValue(applyPolicies, List.class);
config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> {
Policy policy = policyStore.findByName(policyName, resourceServer.getId());
if (policy == null) {
throw new RuntimeException("Policy with name [" + pId + "] not defined.");
throw new RuntimeException("Policy with name [" + policyName + "] not defined.");
}
policyNames = policyNames + "\"" + policy.getId() + "\"";
}
config.put("applyPolicies", "[" + policyNames + "]");
return policy.getId();
}).collect(Collectors.toList())));
} catch (Exception e) {
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
@ -491,6 +429,7 @@ public class ResourceServerService {
}
private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) {
try {
PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
rep.setId(null);
@ -501,113 +440,48 @@ public class ResourceServerService {
String roles = config.get("roles");
if (roles != null && !roles.isEmpty()) {
roles = roles.replace("[", "");
roles = roles.replace("]", "");
if (!roles.isEmpty()) {
String roleNames = "";
for (String role : roles.split(",")) {
if (!roleNames.isEmpty()) {
roleNames = roleNames + ",";
}
role = role.replace("\"", "");
roleNames = roleNames + "\"" + this.realm.getRoleById(role).getName() + "\"";
}
config.put("roles", "[" + roleNames + "]");
}
List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
return roleMap;
}).collect(Collectors.toList())));
}
String users = config.get("users");
if (users != null) {
users = users.replace("[", "");
users = users.replace("]", "");
if (!users.isEmpty()) {
if (users != null && !users.isEmpty()) {
UserFederationManager userManager = this.session.users();
String userNames = "";
for (String user : users.split(",")) {
if (!userNames.isEmpty()) {
userNames = userNames + ",";
}
user = user.replace("\"", "");
userNames = userNames + "\"" + userManager.getUserById(user, this.realm).getUsername() + "\"";
}
config.put("users", "[" + userNames + "]");
}
List<String> userIds = JsonSerialization.readValue(users, List.class);
config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, this.realm).getUsername()).collect(Collectors.toList())));
}
String scopes = config.get("scopes");
if (scopes != null && !scopes.isEmpty()) {
scopes = scopes.replace("[", "");
scopes = scopes.replace("]", "");
if (!scopes.isEmpty()) {
ScopeStore scopeStore = storeFactory.getScopeStore();
String scopeNames = "";
for (String scope : scopes.split(",")) {
if (!scopeNames.isEmpty()) {
scopeNames = scopeNames + ",";
}
scope = scope.replace("\"", "");
scopeNames = scopeNames + "\"" + scopeStore.findById(scope).getName() + "\"";
}
config.put("scopes", "[" + scopeNames + "]");
}
List<String> scopeIds = JsonSerialization.readValue(scopes, List.class);
config.put("scopes", JsonSerialization.writeValueAsString(scopeIds.stream().map(scopeId -> scopeStore.findById(scopeId).getName()).collect(Collectors.toList())));
}
String policyResources = config.get("resources");
if (policyResources != null && !policyResources.isEmpty()) {
policyResources = policyResources.replace("[", "");
policyResources = policyResources.replace("]", "");
if (!policyResources.isEmpty()) {
ResourceStore resourceStore = storeFactory.getResourceStore();
String resourceNames = "";
for (String resource : policyResources.split(",")) {
if (!resourceNames.isEmpty()) {
resourceNames = resourceNames + ",";
List<String> resourceIds = JsonSerialization.readValue(policyResources, List.class);
config.put("resources", JsonSerialization.writeValueAsString(resourceIds.stream().map(resourceId -> resourceStore.findById(resourceId).getName()).collect(Collectors.toList())));
}
resource = resource.replace("\"", "");
resourceNames = resourceNames + "\"" + resourceStore.findById(resource).getName() + "\"";
}
config.put("resources", "[" + resourceNames + "]");
}
}
String policyNames = "";
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
if (!associatedPolicies.isEmpty()) {
for (Policy associatedPolicy : associatedPolicies) {
if (!policyNames.isEmpty()) {
policyNames = policyNames + ",";
config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList())));
}
policyNames = policyNames + "\"" + associatedPolicy.getName() + "\"";
}
config.put("applyPolicies", "[" + policyNames + "]");
}
rep.setAssociatedPolicies(null);
return rep;
} catch (Exception e) {
throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e);
}
}
}

View file

@ -21,14 +21,10 @@ package org.keycloak.authorization.admin.representation;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.admin.util.Models;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.policy.evaluation.Result.PolicyResult;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.authorization.util.Permissions;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -90,9 +86,17 @@ public class PolicyEvaluationResponse {
policies.add(toRepresentation(policy, authorization));
}
if (rep.getResource().getId() != null) {
if (!rep.getScopes().isEmpty()) {
rep.getResource().setName(rep.getResource().getName() + " with scopes " + rep.getScopes().stream().map(ScopeRepresentation::getName).collect(Collectors.toList()));
}
}
rep.setPolicies(policies);
}
resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName()));
response.results = resultsRep;
return response;

View file

@ -63,13 +63,18 @@ public final class Models {
scope.setId(model.getId());
scope.setName(model.getName());
scope.setIconUri(model.getIconUri());
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
scope.setResources(new ArrayList<>());
storeFactory.getResourceStore().findByScope(model.getId()).forEach(resource -> scope.getResources().add(toRepresentation(resource, resource.getResourceServer(), authorizationProvider)));
PolicyStore policyStore = storeFactory.getPolicyStore();
scope.setPolicies(new ArrayList<>());
Set<Policy> policies = new HashSet<>();
policies.addAll(authorizationProvider.getStoreFactory().getPolicyStore().findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()));
for (Policy policyModel : policies) {
policyStore.findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()).forEach(policyModel -> {
PolicyRepresentation policy = new PolicyRepresentation();
policy.setId(policyModel.getId());
@ -79,7 +84,7 @@ public final class Models {
if (!scope.getPolicies().contains(policy)) {
scope.getPolicies().add(policy);
}
}
});
return scope;
}

View file

@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.util.JsonSerialization;
@ -37,6 +38,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -53,61 +55,59 @@ public class KeycloakIdentity implements Identity {
}
public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
this.accessToken = accessToken;
if (this.accessToken == null) {
if (accessToken == null) {
throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
}
if (keycloakSession == null) {
throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN);
}
this.accessToken = accessToken;
this.keycloakSession = keycloakSession;
this.realm = keycloakSession.getContext().getRealm();
HashMap<String, Collection<String>> attributes = new HashMap<>();
Map<String, Collection<String>> attributes = new HashMap<>();
try {
ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
Iterator<String> iterator = objectNode.fieldNames();
List<String> roleNames = new ArrayList<>();
while (iterator.hasNext()) {
String fieldName = iterator.next();
JsonNode fieldValue = objectNode.get(fieldName);
List<String> values = new ArrayList<>();
values.add(fieldValue.asText());
if (fieldValue.isArray()) {
Iterator<JsonNode> valueIterator = fieldValue.iterator();
if (fieldName.equals("realm_access")) {
JsonNode grantedRoles = fieldValue.get("roles");
if (grantedRoles != null) {
Iterator<JsonNode> rolesIt = grantedRoles.iterator();
while (rolesIt.hasNext()) {
roleNames.add(rolesIt.next().asText());
}
while (valueIterator.hasNext()) {
values.add(valueIterator.next().asText());
}
} else {
String value = fieldValue.asText();
if (StringUtil.isNullOrEmpty(value)) {
continue;
}
if (fieldName.equals("resource_access")) {
Iterator<JsonNode> resourceAccessIt = fieldValue.iterator();
while (resourceAccessIt.hasNext()) {
JsonNode grantedRoles = resourceAccessIt.next().get("roles");
if (grantedRoles != null) {
Iterator<JsonNode> rolesIt = grantedRoles.iterator();
while (rolesIt.hasNext()) {
roleNames.add(rolesIt.next().asText());
}
}
}
values.add(value);
}
if (!values.isEmpty()) {
attributes.put(fieldName, values);
}
}
attributes.put("roles", roleNames);
AccessToken.Access realmAccess = accessToken.getRealmAccess();
if (realmAccess != null) {
attributes.put("kc.realm.roles", realmAccess.getRoles());
}
Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
if (resourceAccess != null) {
resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
}
} catch (Exception e) {
throw new RuntimeException("Error while reading attributes from security token.", e);
}

View file

@ -28,6 +28,7 @@ import org.keycloak.authorization.protection.permission.PermissionService;
import org.keycloak.authorization.protection.permission.PermissionsService;
import org.keycloak.authorization.protection.resource.ResourceService;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ErrorResponseException;
@ -48,16 +49,12 @@ public class ProtectionService {
@Path("/resource_set")
public Object resource() {
KeycloakIdentity identity = createIdentity();
if (!identity.hasRole("uma_protection")) {
throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
}
ResourceSetService resourceManager = new ResourceSetService(getResourceServer(identity), this.authorization, null);
ResourceServer resourceServer = getResourceServer(identity);
ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
ResourceService resource = new ResourceService(getResourceServer(identity), identity, resourceManager, this.authorization);
ResourceService resource = new ResourceService(resourceServer, identity, resourceManager, this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@ -68,10 +65,6 @@ public class ProtectionService {
public Object permission() {
KeycloakIdentity identity = createIdentity();
if (!identity.hasRole("uma_protection")) {
throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
}
PermissionService resource = new PermissionService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@ -83,10 +76,6 @@ public class ProtectionService {
public Object permissions() {
KeycloakIdentity identity = createIdentity();
if (!identity.hasRole("uma_protection")) {
throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
}
PermissionsService resource = new PermissionsService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@ -95,7 +84,17 @@ public class ProtectionService {
}
private KeycloakIdentity createIdentity() {
return new KeycloakIdentity(this.authorization.getKeycloakSession());
KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
ResourceServer resourceServer = getResourceServer(identity);
KeycloakSession keycloakSession = authorization.getKeycloakSession();
RealmModel realm = keycloakSession.getContext().getRealm();
ClientModel client = realm.getClientById(resourceServer.getClientId());
if (!identity.hasClientRole(client.getClientId(), "uma_protection")) {
throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
}
return identity;
}
private ResourceServer getResourceServer(Identity identity) {

View file

@ -61,21 +61,39 @@ public final class Permissions {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
return permissions;
}
public static List<ResourcePermission> createResourcePermissions(Resource resource) {
public static List<ResourcePermission> createResourcePermissions(Resource resource, ResourceServer resourceServer, AuthorizationProvider authorization) {
List<ResourcePermission> permissions = new ArrayList<>();
List<Scope> scopes = resource.getScopes();
permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
if (scopes.isEmpty()) {
String type = resource.getType();
// check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource
// is owned by the resource server itself
if (type != null) {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
resourceStore.findByType(type).forEach(resource1 -> {
if (resource1.getOwner().equals(resourceServer.getClientId())) {
scopes.addAll(resource1.getScopes());
}
});
}
}
if (scopes.size() > 1) {
for (Scope scope : scopes) {
permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
}
} else {
permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
}
return permissions;
}

View file

@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl">
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
<div id="content-area" class="col-md-9" role="main">
<div id="content" ng-view/>

View file

@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
}
$scope.Identity = Identity;
});
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
$scope.profile = Profile.get();
});
module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
$scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data;
});
$scope.deleteAlbum = function (album) {
var newAlbum = new Album(album);
newAlbum.$delete({id: album.id}, function () {
new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}

View file

@ -10,7 +10,7 @@
<td>
<ul>
<li data-ng-repeat="p in value">
<a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
<a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
</li>
</ul>
</td>

View file

@ -1,4 +1,4 @@
<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/>
<br/>
@ -15,7 +15,7 @@
</thead>
<tbody>
<tr data-ng-repeat="p in albums">
<td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
<td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
</tr>
</tbody>
</table>

View file

@ -53,7 +53,7 @@
}
],
"realmRoles": [
"user", "admin", "uma_authorization"
"admin", "uma_authorization"
],
"clientRoles": {
"realm-management": [

View file

@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view"
},
{
"name": "urn:photoz.com:scopes:album:create"
"name": "urn:photoz.com:scopes:album:delete"
},
{
"name": "urn:photoz.com:scopes:album:delete"
"name": "urn:photoz.com:scopes:album:create"
}
]
},
@ -44,12 +44,15 @@
"name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something",
"type": "drools",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession",
"mavenArtifactGroupId": "org.keycloak.testsuite",
"mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy",
"applyPolicies": "[]",
"scannerPeriod": "1",
"scannerPeriodUnit": "Hours"
}
@ -58,16 +61,22 @@
"name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something",
"type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"roles": "[\"admin\"]"
"applyPolicies": "[]",
"roles": "[{\"id\":\"admin\",\"required\":true}]"
}
},
{
"name": "Any User Policy",
"description": "Defines that any user can do something",
"type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"roles": "[\"user\"]"
"applyPolicies": "[]",
"roles": "[{\"id\":\"user\"}]"
}
},
{
@ -77,6 +86,7 @@
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n $evaluation.grant();\n}"
}
},
@ -84,6 +94,8 @@
"name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
}
@ -92,35 +104,28 @@
"name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
"applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
}
},
{
"name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org",
"type": "js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
}
},
{
"name": "Only in the Period",
"description": "Access granted only during the morning",
"type": "time",
"config": {
"noa": "2016-01-03 23:59:59",
"expirationUnit": "Minutes",
"nbf": "2016-01-01 00:00:00",
"expirationTime": "1"
}
},
{
"name": "Album Resource Permission",
"description": "General policies that apply to all album resources.",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"defaultResourceType": "http://photoz.com/album",

View file

@ -109,13 +109,7 @@ public class AlbumService {
private void createProtectedResource(Album album) {
try {
HashSet<ScopeRepresentation> scopes = new HashSet<>();
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_CREATE));
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
albumResource.setOwner(album.getUserId());

View file

@ -76,17 +76,28 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
pause(500);
}
public void login(String username, String password) {
public void login(String username, String password) throws InterruptedException {
navigateTo();
Thread.sleep(2000);
if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) {
Thread.sleep(2000);
logOut();
navigateTo();
}
Thread.sleep(2000);
this.loginPage.form().login(username, password);
}
public boolean wasDenied() {
return this.driver.findElement(By.id("output")).getText().contains("You can not access");
}
public void viewAlbum(String name) {
By id = By.id("view-" + name);
WaitUtils.waitUntilElement(id);
this.driver.findElements(id).forEach(WebElement::click);
pause(500);
}
}

View file

@ -16,12 +16,10 @@
*/
package org.keycloak.testsuite.adapter.example.authorization;
import org.apache.commons.io.IOUtils;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
@ -39,7 +37,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.junit.Assert.assertFalse;
@ -144,7 +141,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum();
this.clientPage.deleteAlbum("Alice-Family-Album");
@ -186,7 +182,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum();
assertTrue(this.clientPage.wasDenied());
@ -195,6 +190,121 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
@Test
public void testAdminWithoutPermissionsToTypedResource() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
this.clientPage.login("alice", "alice");
this.clientPage.createAlbum("Alice Family Album");
this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum();
assertFalse(this.clientPage.wasDenied());
this.clientPage.viewAlbum("Alice Family Album");
assertFalse(this.clientPage.wasDenied());
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Album Resource Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Any User Policy\"]");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
this.clientPage.navigateToAdminAlbum();
this.clientPage.viewAlbum("Alice Family Album");
assertTrue(this.clientPage.wasDenied());
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Album Resource Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Any User Policy\", \"Administration Policy\"]");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
this.clientPage.navigateToAdminAlbum();
this.clientPage.viewAlbum("Alice Family Album");
assertFalse(this.clientPage.wasDenied());
this.clientPage.navigateToAdminAlbum();
this.clientPage.deleteAlbum("Alice Family Album");
List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
} finally {
this.deployer.undeploy(RESOURCE_SERVER_ID);
}
}
@Test
public void testAdminWithoutPermissionsToDeleteScopePermission() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
this.clientPage.login("alice", "alice");
this.clientPage.createAlbum("Alice Family Album");
this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum();
assertFalse(this.clientPage.wasDenied());
this.clientPage.deleteAlbum("Alice Family Album");
assertFalse(this.clientPage.wasDenied());
List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Delete Album Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Only Owner Policy\"]");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
this.clientPage.login("alice", "alice");
this.clientPage.createAlbum("Alice Family Album");
this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum();
this.clientPage.viewAlbum("Alice Family Album");
assertFalse(this.clientPage.wasDenied());
resources = getAuthorizationResource().resources().resources();
assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
this.clientPage.navigateToAdminAlbum();
this.clientPage.deleteAlbum("Alice Family Album");
assertTrue(this.clientPage.wasDenied());
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Delete Album Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Only Owner and Administrators Policy\"]");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
this.clientPage.navigateToAdminAlbum();
this.clientPage.deleteAlbum("Alice Family Album");
assertFalse(this.clientPage.wasDenied());
resources = getAuthorizationResource().resources().resources();
assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
} finally {
this.deployer.undeploy(RESOURCE_SERVER_ID);
}
}
private void importResourceServerSettings() throws FileNotFoundException {
getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-restful-api-authz-service.json")), ResourceServerRepresentation.class));
}

View file

@ -49,6 +49,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@ -56,6 +57,8 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.jboss.aesh.terminal.Key.e;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -279,8 +282,12 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
RoleModel adminRole = realm.getRole("admin");
Map role = new HashMap();
role.put("id", adminRole.getId());
try {
config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -352,10 +359,14 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
Policy policy = policyStore.create("Any User Policy", "role", resourceServer);
HashedMap config = new HashedMap();
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
RoleModel adminRole = realm.getRole("user");
RoleModel userRole = realm.getRole("user");
Map role = new HashMap();
role.put("id", userRole.getId());
try {
config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) {
throw new RuntimeException(e);
}

View file

@ -953,6 +953,7 @@ authz-no-resources=No resources
authz-result=Result
authz-authorization-services-enabled=Authorization Enabled
authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client
authz-required=Required
# Authz Settings
authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server.
@ -1016,7 +1017,9 @@ authz-select-a-policy=Select a policy
# Authz Role Policy Detail
authz-add-role-policy=Add Role Policy
authz-no-roles-assigned=No roles assigned.
authz-policy-role-roles.tooltip=Specifies which role(s) are allowed by this policy.
authz-policy-role-realm-roles.tooltip=Specifies which *realm* role(s) are allowed by this policy.
authz-policy-role-clients.tooltip=Selects a client in order to filter the client roles that can be applied to this policy.
authz-policy-role-client-roles.tooltip=Specifies which *client* role(s) are allowed by this policy.
# Authz User Policy Detail
authz-add-user-policy=Add User Policy

View file

@ -150,6 +150,14 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
client : client.id,
rsrid : $route.current.params.rsrid,
}, function(data) {
if (!data.scopes) {
data.scopes = [];
}
if (!data.policies) {
data.policies = [];
}
$scope.resource = angular.copy(data);
$scope.changed = false;
@ -157,9 +165,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
$scope.resource.scopes[i] = $scope.resource.scopes[i].name;
}
data = angular.copy($scope.resource);
$scope.originalResource = data;
$scope.originalResource = angular.copy($scope.resource);
$scope.$watch('resource', function() {
if (!angular.equals($scope.resource, data)) {
@ -237,6 +243,10 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo
ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) {
$scope.scopes = data;
});
$scope.createPolicy = function(scope) {
$location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id});
}
});
});
@ -534,7 +544,7 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro
}, realm, client, $scope);
});
module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, $location, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
PolicyController.onInit({
getPolicyType : function() {
return "scope";
@ -624,6 +634,12 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
newPolicy.decisionStrategy = 'UNANIMOUS';
newPolicy.config = {};
newPolicy.config.resources = '';
var scopeId = $location.search()['scpid'];
if (scopeId) {
newPolicy.config.scopes = [scopeId];
}
},
onCreate : function() {
@ -712,7 +728,7 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route,
}, realm, client, $scope);
});
module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, PolicyController, Role, RoleById) {
module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, Client, ClientRole, PolicyController, Role, RoleById) {
PolicyController.onInit({
getPolicyType : function() {
return "role";
@ -723,6 +739,10 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.roles = data;
});
Client.query({realm: $route.current.params.realm}, function (data) {
$scope.clients = data;
});
$scope.selectedRoles = [];
$scope.selectRole = function(role) {
@ -732,10 +752,55 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.selectedRole = {};
$scope.selectedRoles.push(role);
var clientRoles = [];
if ($scope.clientRoles) {
for (i = 0; i < $scope.clientRoles.length; i++) {
if ($scope.clientRoles[i].id != role.id) {
clientRoles.push($scope.clientRoles[i]);
}
}
$scope.clientRoles = clientRoles;
}
}
$scope.removeFromList = function(list, index) {
list.splice(index, 1);
$scope.removeFromList = function(role) {
if ($scope.clientRoles && $scope.selectedClient && $scope.selectedClient.id == role.containerId) {
$scope.clientRoles.push(role);
}
var index = $scope.selectedRoles.indexOf(role);
if (index != -1) {
$scope.selectedRoles.splice(index, 1);
}
}
$scope.selectClient = function() {
if (!$scope.selectedClient) {
$scope.clientRoles = [];
return;
}
ClientRole.query({realm: $route.current.params.realm, client: $scope.selectedClient.id}, function(data) {
var roles = [];
for (j = 0; j < data.length; j++) {
var defined = false;
for (i = 0; i < $scope.selectedRoles.length; i++) {
if ($scope.selectedRoles[i].id == data[j].id) {
defined = true;
break;
}
}
if (!defined) {
data[j].container = {};
data[j].container.name = $scope.selectedClient.clientId;
roles.push(data[j]);
}
}
$scope.clientRoles = roles;
});
}
},
@ -746,7 +811,18 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = eval(policy.config.roles);
for (i = 0; i < roles.length; i++) {
RoleById.get({realm: $route.current.params.realm, role: roles[i]}, function(data) {
RoleById.get({realm: $route.current.params.realm, role: roles[i].id}, function(data) {
for (i = 0; i < roles.length; i++) {
if (roles[i].id == data.id) {
data.required = roles[i].required ? true : false;
}
}
for (i = 0; i < $scope.clients.length; i++) {
if ($scope.clients[i].id == data.containerId) {
data.container = {};
data.container.name = $scope.clients[i].clientId;
}
}
selectedRoles.push(data);
$scope.selectedRoles = angular.copy(selectedRoles);
});
@ -764,7 +840,12 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) {
roles.push($scope.selectedRoles[i].id);
var role = {};
role.id = $scope.selectedRoles[i].id;
if ($scope.selectedRoles[i].required) {
role.required = $scope.selectedRoles[i].required;
}
roles.push(role);
}
$scope.policy.config.roles = JSON.stringify(roles);
@ -774,12 +855,35 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) {
roles.push($scope.selectedRoles[i].id);
var role = {};
role.id = $scope.selectedRoles[i].id;
if ($scope.selectedRoles[i].required) {
role.required = $scope.selectedRoles[i].required;
}
roles.push(role);
}
$scope.policy.config.roles = JSON.stringify(roles);
}
}, realm, client, $scope);
$scope.hasRealmRole = function () {
for (i = 0; i < $scope.selectedRoles.length; i++) {
if (!$scope.selectedRoles[i].clientRole) {
return true;
}
}
return false;
}
$scope.hasClientRole = function () {
for (i = 0; i < $scope.selectedRoles.length; i++) {
if ($scope.selectedRoles[i].clientRole) {
return true;
}
}
return false;
}
});
module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) {

View file

@ -49,31 +49,85 @@
<kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="roles">{{:: 'roles' | translate}} <span class="required">*</span></label>
<label class="col-md-2 control-label" for="roles">{{:: 'realm-roles' | translate}} <span class="required">*</span></label>
<div class="col-md-6">
<div class="col-md-4">
<select ui-select2="{ minimumInputLength: 1}" id="roles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
ng-options="role as role.name for role in roles" data-ng-required="selectedUsers.length == 0 && selectedRoles.length == 0">
</select>
</div>
<kc-tooltip>{{:: 'authz-policy-role-roles.tooltip' | translate}}</kc-tooltip>
<kc-tooltip>{{:: 'authz-policy-role-realm-roles.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">
<div class="col-sm-4" data-ng-show="hasRealmRole()">
<table class="table table-striped table-bordered">
<thead>
<tr data-ng-hide="!selectedRoles.length">
<th>{{:: 'name' | translate}}</th>
<tr>
<th class="col-sm-5">{{:: 'name' | translate}}</th>
<th>{{:: 'authz-required' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="role in selectedRoles | orderBy:'name'">
<tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="!role.clientRole">
<td>{{role.name}}</td>
<td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
<td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedRoles, $index);">{{:: 'remove' | translate}}</button>
<button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
</td>
</tr>
<tr data-ng-show="!selectedRoles.length">
<td class="text-muted" colspan="3">{{:: 'authz-no-roles-assigned' | translate}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clients">{{:: 'clients' | translate}}</label>
<div class="col-md-4">
<select class="form-control" id="clients"
ng-model="selectedClient"
ng-change="selectClient()"
data-ng-options="current as current.clientId for current in clients">
<option value="">{{:: 'selectOne' | translate}}...</option>
</select>
</div>
<kc-tooltip>{{:: 'authz-policy-role-clients.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clientRoles">{{:: 'client-roles' | translate}} <span class="required">*</span></label>
<div class="col-md-4">
<select ui-select2="{ minimumInputLength: 1}" id="clientRoles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
ng-options="role as role.name for role in clientRoles" data-ng-required="selectedRoles.length == 0" data-ng-disabled="!selectedClient">
</select>
</div>
<kc-tooltip>{{:: 'authz-policy-role-client-roles.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-4" data-ng-show="hasClientRole()">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="col-sm-5">{{:: 'name' | translate}}</th>
<th class="col-sm-5">{{:: 'client' | translate}}</th>
<th>{{:: 'authz-required' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="role.clientRole">
<td>{{role.name}}</td>
<td>{{role.container.name}}</td>
<td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
<td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
</td>
</tr>
<tr data-ng-show="!selectedRoles.length">
@ -98,7 +152,6 @@
</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>

View file

@ -43,14 +43,14 @@
<ul>
<li data-ng-repeat="policyResult in result.policies">
<strong><a
href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
decision was <span style="color: green" data-ng-show="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
<span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision.</a>
<ul>
<li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
<strong><a
href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
voted to <span style="color: green"
data-ng-show="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>
<span style="color: red" data-ng-hide="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>.</a>

View file

@ -24,11 +24,33 @@
</tr>
<tr data-ng-hide="scopes.length == 0">
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'authz-resources' | translate}}</th>
<th>{{:: 'authz-permissions' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="scope in scopes | filter:search | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>
<td>
<span data-ng-show="!scope.resources.length">{{:: 'authz-no-resources-assigned' | translate}}</span>
<span data-ng-show="scope.resources.length > 0">
<span ng-repeat="resource in scope.resources">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource/{{resource._id}}">{{resource.name}}</a>{{$last ? '' : ', '}}
</span>
</span>
</td>
<td>
<span data-ng-show="!scope.policies.length">{{:: 'authz-no-permission-assigned' | translate}}</span>
<span data-ng-show="scope.policies.length > 0">
<span ng-repeat="policy in scope.policies">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a>{{$last ? '' : ', '}}
</span>
</span>
</td>
<td class="kc-action-cell" style="vertical-align: middle">
<button class="btn btn-default btn-block btn-sm" ng-click="createPolicy(scope);">{{:: 'authz-create-permission' | translate}}</button>
</td>
</tr>
<tr data-ng-show="(scopes | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>