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

View file

@ -34,7 +34,9 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -81,11 +83,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
RoleModel removedRole = ((RoleRemovedEvent) event).getRole(); RoleModel removedRole = ((RoleRemovedEvent) event).getRole();
policyStore.findByType(getId()).forEach(policy -> { policyStore.findByType(getId()).forEach(policy -> {
List<String> roles = new ArrayList<>(); List<Map> roles = new ArrayList<>();
for (String roleId : getRoles(policy)) { for (Map<String,Object> role : getRoles(policy)) {
if (!roleId.equals(removedRole.getId())) { if (!role.get("id").equals(removedRole.getId())) {
roles.add(roleId); 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()); policyStore.delete(policy.getId());
} else { } 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) { } catch (IOException e) {
throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e); throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e);
@ -116,17 +126,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
return "role"; return "role";
} }
static String[] getRoles(Policy policy) { static Map<String, Object>[] getRoles(Policy policy) {
String roles = policy.getConfig().get("roles"); String roles = policy.getConfig().get("roles");
if (roles != null) { if (roles != null) {
try { try {
return JsonSerialization.readValue(roles.getBytes(), String[].class); return JsonSerialization.readValue(roles.getBytes(), Map[].class);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", 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 scopeParamRequired;
protected boolean composite; protected boolean composite;
protected Composites composites; protected Composites composites;
private Boolean clientRole;
private String containerId;
public static class Composites { public static class Composites {
protected Set<String> realm; protected Set<String> realm;
@ -122,4 +124,20 @@ public class RoleRepresentation {
public void setComposite(boolean composite) { public void setComposite(boolean composite) {
this.composite = 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; package org.keycloak.representations.idm.authorization;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URI; import java.net.URI;
@ -39,12 +40,14 @@ public class ResourceRepresentation {
private String name; private String name;
private String uri; private String uri;
private String type; private String type;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<ScopeRepresentation> scopes; private Set<ScopeRepresentation> scopes;
@JsonProperty("icon_uri") @JsonProperty("icon_uri")
private String iconUri; private String iconUri;
private ResourceOwnerRepresentation owner; private ResourceOwnerRepresentation owner;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PolicyRepresentation> policies; private List<PolicyRepresentation> policies;
/** /**

View file

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

View file

@ -1,4 +1,6 @@
{ {
"allowRemoteResourceManagement": false,
"policyEnforcementMode": "ENFORCING",
"resources": [ "resources": [
{ {
"name": "Default Resource", "name": "Default Resource",
@ -8,22 +10,26 @@
], ],
"policies": [ "policies": [
{ {
"name": "Only From Realm Policy", "name": "Default Policy",
"description": "A policy that grants access only for users within this realm", "description": "A policy that grants access only for users within this realm",
"type": "js", "type": "js",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": { "config": {
"applyPolicies": "[]", "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", "name": "Default Permission",
"description": "A permission that applies to the default resource type", "description": "A permission that applies to the default resource type",
"type": "resource", "type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"defaultResourceType": "urn:hello-world-authz-service:resources:default", "defaultResourceType": "urn:hello-world-authz-service:resources:default",
"default": "true", "default": "true",
"applyPolicies": "[\"Only From Realm Policy\"]" "applyPolicies": "[\"Default Policy\"]"
} }
} }
] ]

View file

@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl"> <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-area" class="col-md-9" role="main">
<div id="content" ng-view/> <div id="content" ng-view/>

View file

@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () { $scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {}); Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
} }
$scope.Identity = Identity;
}); });
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) { 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(); $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 = {}; $scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) { $http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data; $scope.albums = data;
}); });
$scope.deleteAlbum = function (album) { $scope.deleteAlbum = function (album) {
var newAlbum = new Album(album); new Album(album).$delete({id: album.id}, function () {
newAlbum.$delete({id: album.id}, function () {
$route.reload(); $route.reload();
}); });
} }

View file

@ -10,7 +10,7 @@
<td> <td>
<ul> <ul>
<li data-ng-repeat="p in value"> <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> </li>
</ul> </ul>
</td> </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> <div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/> <hr/>
<br/> <br/>
@ -15,7 +15,7 @@
</thead> </thead>
<tbody> <tbody>
<tr data-ng-repeat="p in albums"> <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> </tr>
</tbody> </tbody>
</table> </table>

View file

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

View file

@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view" "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", "name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something", "description": "Defines that only the resource owner is allowed to do something",
"type": "drools", "type": "drools",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT", "mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy", "mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession", "sessionName": "MainOwnerSession",
"mavenArtifactGroupId": "org.keycloak", "mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy", "moduleName": "PhotozAuthzOwnerPolicy",
"applyPolicies": "[]",
"scannerPeriod": "1", "scannerPeriod": "1",
"scannerPeriodUnit": "Hours" "scannerPeriodUnit": "Hours"
} }
@ -58,16 +61,22 @@
"name": "Any Admin Policy", "name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something", "description": "Defines that adminsitrators can do something",
"type": "role", "type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"roles": "[\"admin\"]" "applyPolicies": "[]",
"roles": "[{\"id\":\"admin\",\"required\":true}]"
} }
}, },
{ {
"name": "Any User Policy", "name": "Any User Policy",
"description": "Defines that any user can do something", "description": "Defines that any user can do something",
"type": "role", "type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"roles": "[\"user\"]" "applyPolicies": "[]",
"roles": "[{\"id\":\"user\"}]"
} }
}, },
{ {
@ -77,6 +86,7 @@
"logic": "POSITIVE", "logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS", "decisionStrategy": "UNANIMOUS",
"config": { "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}" "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", "name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.", "description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate", "type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]" "applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
} }
@ -92,35 +104,28 @@
"name": "Only Owner and Administrators Policy", "name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something", "description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate", "type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE", "decisionStrategy": "AFFIRMATIVE",
"config": { "config": {
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]" "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
} }
}, },
{ {
"name": "Only From @keycloak.org or Admin", "name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org", "description": "Defines that only users from @keycloak.org",
"type": "js", "type": "js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"applyPolicies": "[]", "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}" "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", "name": "Album Resource Permission",
"description": "General policies that apply to all album resources.", "description": "General policies that apply to all album resources.",
"type": "resource", "type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE", "decisionStrategy": "AFFIRMATIVE",
"config": { "config": {
"defaultResourceType": "http://photoz.com/album", "defaultResourceType": "http://photoz.com/album",

View file

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

View file

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

View file

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

View file

@ -98,7 +98,7 @@ public class JPAResourceStore implements ResourceStore {
@Override @Override
public List<Resource> findByScope(String... id) { 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)); query.setParameter("scopeIds", Arrays.asList(id));

View file

@ -95,7 +95,7 @@ public interface Attributes {
private final String[] values; private final String[] values;
private final String name; private final String name;
Entry(String name, Collection<String> values) { public Entry(String name, Collection<String> values) {
this.name = name; this.name = name;
this.values = values.toArray(new String[values.size()]); 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 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. * <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(); 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 * @param roleName the name of the role
* *
* @return true if the identity has the given role. Otherwise, it returns false. * @return true if the identity has the given role. Otherwise, it returns false.
*/ */
default boolean hasRole(String roleName) { 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; 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.migration.ModelVersion;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.UserModel; 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) { public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) { for (RealmModel realm : session.realms().getRealms()) {
migrateDefaultRequiredAction(realm); migrateDefaultRequiredAction(realm);
migrateRolePolicies(realm, session);
} }
} }
@ -46,4 +58,46 @@ public class MigrateTo2_1_0 {
otpAction.setName("Configure OTP"); 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.setDescription(role.getDescription());
rep.setScopeParamRequired(role.isScopeParamRequired()); rep.setScopeParamRequired(role.isScopeParamRequired());
rep.setComposite(role.isComposite()); rep.setComposite(role.isComposite());
rep.setClientRole(role.isClientRole());
rep.setContainerId(role.getContainerId());
return rep; return rep;
} }

View file

@ -136,11 +136,19 @@ public class PolicyEvaluationService {
} }
private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) { 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 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(); Set<String> givenScopes = resource.getScopes();
if (givenScopes == null) { if (givenScopes == null) {
@ -157,7 +165,13 @@ public class PolicyEvaluationService {
} else if (resource.getType() != null) { } else if (resource.getType() != null) {
return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer)); return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer));
} else { } 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()); }).collect(Collectors.toList());
} }

View file

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

View file

@ -17,8 +17,6 @@
*/ */
package org.keycloak.authorization.admin; 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.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.util.Models; import org.keycloak.authorization.admin.util.Models;
@ -146,7 +144,11 @@ public class ResourceServerService {
.stream().map(resource -> { .stream().map(resource -> {
ResourceRepresentation rep = Models.toRepresentation(resource, resourceServer, authorization); ResourceRepresentation rep = Models.toRepresentation(resource, resourceServer, authorization);
rep.getOwner().setId(null); if (rep.getOwner().getId().equals(this.resourceServer.getClientId())) {
rep.setOwner(null);
} else {
rep.getOwner().setId(null);
}
rep.setId(null); rep.setId(null);
rep.setPolicies(null); rep.setPolicies(null);
rep.getScopes().forEach(scopeRepresentation -> { rep.getScopes().forEach(scopeRepresentation -> {
@ -175,15 +177,8 @@ public class ResourceServerService {
ScopeRepresentation rep = Models.toRepresentation(scope, authorization); ScopeRepresentation rep = Models.toRepresentation(scope, authorization);
rep.setId(null); rep.setId(null);
rep.setPolicies(null);
rep.getPolicies().forEach(policyRepresentation -> { rep.setResources(null);
policyRepresentation.setId(null);
policyRepresentation.setConfig(null);
policyRepresentation.setType(null);
policyRepresentation.setDecisionStrategy(null);
policyRepresentation.setDescription(null);
policyRepresentation.setDependentPolicies(null);
});
return rep; return rep;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
@ -258,131 +253,74 @@ public class ResourceServerService {
String roles = config.get("roles"); String roles = config.get("roles");
if (roles != null && !roles.isEmpty()) { if (roles != null && !roles.isEmpty()) {
roles = roles.replace("[", ""); try {
roles = roles.replace("]", ""); List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> {
if (!roles.isEmpty()) { roleConfig.put("id", realm.getRole(roleConfig.get("id").toString()).getId());
String roleNames = ""; return roleConfig;
}).collect(Collectors.toList())));
for (String role : roles.split(",")) { } catch (Exception e) {
if (!roleNames.isEmpty()) { throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
roleNames = roleNames + ",";
}
role = role.replace("\"", "");
roleNames = roleNames + "\"" + this.realm.getRole(role).getId() + "\"";
}
config.put("roles", "[" + roleNames + "]");
} }
} }
String users = config.get("users"); String users = config.get("users");
if (users != null) { if (users != null && !users.isEmpty()) {
users = users.replace("[", ""); try {
users = users.replace("]", ""); 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())));
if (!users.isEmpty()) { } catch (Exception e) {
String userNames = ""; throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
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 + "]");
} }
} }
String scopes = config.get("scopes"); String scopes = config.get("scopes");
if (scopes != null && !scopes.isEmpty()) { if (scopes != null && !scopes.isEmpty()) {
scopes = scopes.replace("[", ""); try {
scopes = scopes.replace("]", ""); List<String> scopesMap = JsonSerialization.readValue(scopes, List.class);
config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> {
if (!scopes.isEmpty()) { Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId());
String scopeNames = "";
for (String scope : scopes.split(",")) {
if (!scopeNames.isEmpty()) {
scopeNames = scopeNames + ",";
}
scope = scope.replace("\"", "");
Scope newScope = scopeStore.findByName(scope, resourceServer.getId());
if (newScope == null) { 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() + "\""; return newScope.getId();
} }).collect(Collectors.toList())));
} catch (Exception e) {
config.put("scopes", "[" + scopeNames + "]"); throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
} }
} }
String policyResources = config.get("resources"); String policyResources = config.get("resources");
if (policyResources != null && !policyResources.isEmpty()) { if (policyResources != null && !policyResources.isEmpty()) {
policyResources = policyResources.replace("[", ""); try {
policyResources = policyResources.replace("]", ""); 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())));
if (!policyResources.isEmpty()) { } catch (Exception e) {
String resourceNames = ""; throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
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 + "]");
} }
} }
String applyPolicies = config.get("applyPolicies"); String applyPolicies = config.get("applyPolicies");
if (applyPolicies != null && !applyPolicies.isEmpty()) { if (applyPolicies != null && !applyPolicies.isEmpty()) {
applyPolicies = applyPolicies.replace("[", ""); try {
applyPolicies = applyPolicies.replace("]", ""); List<String> policies = JsonSerialization.readValue(applyPolicies, List.class);
config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> {
if (!applyPolicies.isEmpty()) { Policy policy = policyStore.findByName(policyName, resourceServer.getId());
String policyNames = "";
for (String pId : applyPolicies.split(",")) {
if (!policyNames.isEmpty()) {
policyNames = policyNames + ",";
}
pId = pId.replace("\"", "").trim();
Policy policy = policyStore.findByName(pId, resourceServer.getId());
if (policy == null) { 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() + "\""; return policy.getId();
} }).collect(Collectors.toList())));
} catch (Exception e) {
config.put("applyPolicies", "[" + policyNames + "]"); throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
} }
} }
@ -491,123 +429,59 @@ public class ResourceServerService {
} }
private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) { private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) {
PolicyRepresentation rep = Models.toRepresentation(policy, authorization); try {
PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
rep.setId(null); rep.setId(null);
rep.setDependentPolicies(null); rep.setDependentPolicies(null);
Map<String, String> config = rep.getConfig(); Map<String, String> config = rep.getConfig();
String roles = config.get("roles"); String roles = config.get("roles");
if (roles != null && !roles.isEmpty()) { if (roles != null && !roles.isEmpty()) {
roles = roles.replace("[", ""); List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
roles = roles.replace("]", ""); config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
if (!roles.isEmpty()) { return roleMap;
String roleNames = ""; }).collect(Collectors.toList())));
for (String role : roles.split(",")) {
if (!roleNames.isEmpty()) {
roleNames = roleNames + ",";
}
role = role.replace("\"", "");
roleNames = roleNames + "\"" + this.realm.getRoleById(role).getName() + "\"";
}
config.put("roles", "[" + roleNames + "]");
} }
}
String users = config.get("users"); String users = config.get("users");
if (users != null) { if (users != null && !users.isEmpty()) {
users = users.replace("[", "");
users = users.replace("]", "");
if (!users.isEmpty()) {
UserFederationManager userManager = this.session.users(); UserFederationManager userManager = this.session.users();
String 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())));
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 + "]");
} }
}
String scopes = config.get("scopes"); String scopes = config.get("scopes");
if (scopes != null && !scopes.isEmpty()) { if (scopes != null && !scopes.isEmpty()) {
scopes = scopes.replace("[", "");
scopes = scopes.replace("]", "");
if (!scopes.isEmpty()) {
ScopeStore scopeStore = storeFactory.getScopeStore(); ScopeStore scopeStore = storeFactory.getScopeStore();
String 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())));
for (String scope : scopes.split(",")) {
if (!scopeNames.isEmpty()) {
scopeNames = scopeNames + ",";
}
scope = scope.replace("\"", "");
scopeNames = scopeNames + "\"" + scopeStore.findById(scope).getName() + "\"";
}
config.put("scopes", "[" + scopeNames + "]");
} }
}
String policyResources = config.get("resources"); String policyResources = config.get("resources");
if (policyResources != null && !policyResources.isEmpty()) { if (policyResources != null && !policyResources.isEmpty()) {
policyResources = policyResources.replace("[", "");
policyResources = policyResources.replace("]", "");
if (!policyResources.isEmpty()) {
ResourceStore resourceStore = storeFactory.getResourceStore(); ResourceStore resourceStore = storeFactory.getResourceStore();
String 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())));
for (String resource : policyResources.split(",")) {
if (!resourceNames.isEmpty()) {
resourceNames = resourceNames + ",";
}
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 + ",";
}
policyNames = policyNames + "\"" + associatedPolicy.getName() + "\"";
} }
config.put("applyPolicies", "[" + policyNames + "]"); Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
}
return rep; if (!associatedPolicies.isEmpty()) {
config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList())));
}
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.AuthorizationProvider;
import org.keycloak.authorization.Decision.Effect; import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.admin.util.Models; import org.keycloak.authorization.admin.util.Models;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.policy.evaluation.Result.PolicyResult; 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.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -90,9 +86,17 @@ public class PolicyEvaluationResponse {
policies.add(toRepresentation(policy, authorization)); 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); rep.setPolicies(policies);
} }
resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName()));
response.results = resultsRep; response.results = resultsRep;
return response; return response;

View file

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

View file

@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -37,6 +38,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @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) { public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
this.accessToken = accessToken; if (accessToken == null) {
if (this.accessToken == null) {
throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN); 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.keycloakSession = keycloakSession;
this.realm = keycloakSession.getContext().getRealm(); this.realm = keycloakSession.getContext().getRealm();
HashMap<String, Collection<String>> attributes = new HashMap<>(); Map<String, Collection<String>> attributes = new HashMap<>();
try { try {
ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken); ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
Iterator<String> iterator = objectNode.fieldNames(); Iterator<String> iterator = objectNode.fieldNames();
List<String> roleNames = new ArrayList<>();
while (iterator.hasNext()) { while (iterator.hasNext()) {
String fieldName = iterator.next(); String fieldName = iterator.next();
JsonNode fieldValue = objectNode.get(fieldName); JsonNode fieldValue = objectNode.get(fieldName);
List<String> values = new ArrayList<>(); List<String> values = new ArrayList<>();
values.add(fieldValue.asText()); if (fieldValue.isArray()) {
Iterator<JsonNode> valueIterator = fieldValue.iterator();
if (fieldName.equals("realm_access")) { while (valueIterator.hasNext()) {
JsonNode grantedRoles = fieldValue.get("roles"); values.add(valueIterator.next().asText());
if (grantedRoles != null) {
Iterator<JsonNode> rolesIt = grantedRoles.iterator();
while (rolesIt.hasNext()) {
roleNames.add(rolesIt.next().asText());
}
} }
} else {
String value = fieldValue.asText();
if (StringUtil.isNullOrEmpty(value)) {
continue;
}
values.add(value);
} }
if (fieldName.equals("resource_access")) { if (!values.isEmpty()) {
Iterator<JsonNode> resourceAccessIt = fieldValue.iterator(); attributes.put(fieldName, values);
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());
}
}
}
} }
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) { } catch (Exception e) {
throw new RuntimeException("Error while reading attributes from security token.", 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.permission.PermissionsService;
import org.keycloak.authorization.protection.resource.ResourceService; import org.keycloak.authorization.protection.resource.ResourceService;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
@ -48,16 +49,12 @@ public class ProtectionService {
@Path("/resource_set") @Path("/resource_set")
public Object resource() { public Object resource() {
KeycloakIdentity identity = createIdentity(); KeycloakIdentity identity = createIdentity();
ResourceServer resourceServer = getResourceServer(identity);
if (!identity.hasRole("uma_protection")) { ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
}
ResourceSetService resourceManager = new ResourceSetService(getResourceServer(identity), this.authorization, null);
ResteasyProviderFactory.getInstance().injectProperties(resourceManager); 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); ResteasyProviderFactory.getInstance().injectProperties(resource);
@ -68,10 +65,6 @@ public class ProtectionService {
public Object permission() { public Object permission() {
KeycloakIdentity identity = createIdentity(); 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); PermissionService resource = new PermissionService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource); ResteasyProviderFactory.getInstance().injectProperties(resource);
@ -83,10 +76,6 @@ public class ProtectionService {
public Object permissions() { public Object permissions() {
KeycloakIdentity identity = createIdentity(); 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); PermissionsService resource = new PermissionsService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource); ResteasyProviderFactory.getInstance().injectProperties(resource);
@ -95,7 +84,17 @@ public class ProtectionService {
} }
private KeycloakIdentity createIdentity() { 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) { private ResourceServer getResourceServer(Identity identity) {

View file

@ -61,20 +61,38 @@ public final class Permissions {
StoreFactory storeFactory = authorization.getStoreFactory(); StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore(); ResourceStore resourceStore = storeFactory.getResourceStore();
resourceStore.findByOwner(resourceServer.getClientId()).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))); resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
return permissions; 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<ResourcePermission> permissions = new ArrayList<>();
List<Scope> scopes = resource.getScopes(); List<Scope> scopes = resource.getScopes();
permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer())); if (scopes.isEmpty()) {
String type = resource.getType();
for (Scope scope : scopes) { // check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource
permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer())); // 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; return permissions;

View file

@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl"> <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-area" class="col-md-9" role="main">
<div id="content" ng-view/> <div id="content" ng-view/>

View file

@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () { $scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {}); Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
} }
$scope.Identity = Identity;
}); });
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) { 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(); $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 = {}; $scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) { $http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data; $scope.albums = data;
}); });
$scope.deleteAlbum = function (album) { $scope.deleteAlbum = function (album) {
var newAlbum = new Album(album); new Album(album).$delete({id: album.id}, function () {
newAlbum.$delete({id: album.id}, function () {
$route.reload(); $route.reload();
}); });
} }

View file

@ -1,19 +1,19 @@
<h1>All Albums</h1> <h1>All Albums</h1>
<table class="table" data-ng-repeat="(key, value) in albums"> <table class="table" data-ng-repeat="(key, value) in albums">
<thead> <thead>
<tr> <tr>
<th>{{key}}</th> <th>{{key}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<ul> <ul>
<li data-ng-repeat="p in value"> <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> </li>
</ul> </ul>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

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> <div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/> <hr/>
<br/> <br/>
@ -9,14 +9,14 @@
<span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span> <span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span>
<table class="table" data-ng-show="albums.length > 0"> <table class="table" data-ng-show="albums.length > 0">
<thead> <thead>
<tr> <tr>
<th>Your Albums</th> <th>Your Albums</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr data-ng-repeat="p in albums"> <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> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View file

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

View file

@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view" "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", "name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something", "description": "Defines that only the resource owner is allowed to do something",
"type": "drools", "type": "drools",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT", "mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy", "mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession", "sessionName": "MainOwnerSession",
"mavenArtifactGroupId": "org.keycloak.testsuite", "mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy", "moduleName": "PhotozAuthzOwnerPolicy",
"applyPolicies": "[]",
"scannerPeriod": "1", "scannerPeriod": "1",
"scannerPeriodUnit": "Hours" "scannerPeriodUnit": "Hours"
} }
@ -58,16 +61,22 @@
"name": "Any Admin Policy", "name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something", "description": "Defines that adminsitrators can do something",
"type": "role", "type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"roles": "[\"admin\"]" "applyPolicies": "[]",
"roles": "[{\"id\":\"admin\",\"required\":true}]"
} }
}, },
{ {
"name": "Any User Policy", "name": "Any User Policy",
"description": "Defines that any user can do something", "description": "Defines that any user can do something",
"type": "role", "type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"roles": "[\"user\"]" "applyPolicies": "[]",
"roles": "[{\"id\":\"user\"}]"
} }
}, },
{ {
@ -77,6 +86,7 @@
"logic": "POSITIVE", "logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS", "decisionStrategy": "UNANIMOUS",
"config": { "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}" "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", "name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.", "description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate", "type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]" "applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
} }
@ -92,35 +104,28 @@
"name": "Only Owner and Administrators Policy", "name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something", "description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate", "type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE", "decisionStrategy": "AFFIRMATIVE",
"config": { "config": {
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]" "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
} }
}, },
{ {
"name": "Only From @keycloak.org or Admin", "name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org", "description": "Defines that only users from @keycloak.org",
"type": "js", "type": "js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": { "config": {
"applyPolicies": "[]", "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}" "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", "name": "Album Resource Permission",
"description": "General policies that apply to all album resources.", "description": "General policies that apply to all album resources.",
"type": "resource", "type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE", "decisionStrategy": "AFFIRMATIVE",
"config": { "config": {
"defaultResourceType": "http://photoz.com/album", "defaultResourceType": "http://photoz.com/album",

View file

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

View file

@ -76,17 +76,28 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
pause(500); pause(500);
} }
public void login(String username, String password) { public void login(String username, String password) throws InterruptedException {
navigateTo(); navigateTo();
Thread.sleep(2000);
if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) { if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) {
Thread.sleep(2000);
logOut(); logOut();
navigateTo();
} }
Thread.sleep(2000);
this.loginPage.form().login(username, password); this.loginPage.form().login(username, password);
} }
public boolean wasDenied() { public boolean wasDenied() {
return this.driver.findElement(By.id("output")).getText().contains("You can not access"); 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; 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.Deployer;
import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.AuthorizationResource;
@ -39,7 +37,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.junit.Assert.assertFalse; 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.navigateToAdminAlbum();
this.clientPage.deleteAlbum("Alice-Family-Album"); this.clientPage.deleteAlbum("Alice-Family-Album");
@ -186,7 +182,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
} }
this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum(); this.clientPage.navigateToAdminAlbum();
assertTrue(this.clientPage.wasDenied()); 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 { 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)); 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 javax.ws.rs.core.MultivaluedMap;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -56,6 +57,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.jboss.aesh.terminal.Key.e;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @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); RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
RoleModel adminRole = realm.getRole("admin"); RoleModel adminRole = realm.getRole("admin");
Map role = new HashMap();
role.put("id", adminRole.getId());
try { try {
config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()})); config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -352,10 +359,14 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
Policy policy = policyStore.create("Any User Policy", "role", resourceServer); Policy policy = policyStore.create("Any User Policy", "role", resourceServer);
HashedMap config = new HashedMap(); HashedMap config = new HashedMap();
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME); 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 { try {
config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()})); config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View file

@ -953,6 +953,7 @@ authz-no-resources=No resources
authz-result=Result authz-result=Result
authz-authorization-services-enabled=Authorization Enabled authz-authorization-services-enabled=Authorization Enabled
authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client
authz-required=Required
# Authz Settings # Authz Settings
authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server. 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 Role Policy Detail
authz-add-role-policy=Add Role Policy authz-add-role-policy=Add Role Policy
authz-no-roles-assigned=No roles assigned. 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 User Policy Detail
authz-add-user-policy=Add User Policy authz-add-user-policy=Add User Policy

View file

@ -150,6 +150,14 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
client : client.id, client : client.id,
rsrid : $route.current.params.rsrid, rsrid : $route.current.params.rsrid,
}, function(data) { }, function(data) {
if (!data.scopes) {
data.scopes = [];
}
if (!data.policies) {
data.policies = [];
}
$scope.resource = angular.copy(data); $scope.resource = angular.copy(data);
$scope.changed = false; $scope.changed = false;
@ -157,9 +165,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
$scope.resource.scopes[i] = $scope.resource.scopes[i].name; $scope.resource.scopes[i] = $scope.resource.scopes[i].name;
} }
data = angular.copy($scope.resource); $scope.originalResource = angular.copy($scope.resource);
$scope.originalResource = data;
$scope.$watch('resource', function() { $scope.$watch('resource', function() {
if (!angular.equals($scope.resource, data)) { 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) { ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) {
$scope.scopes = 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); }, 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({ PolicyController.onInit({
getPolicyType : function() { getPolicyType : function() {
return "scope"; return "scope";
@ -624,6 +634,12 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
newPolicy.decisionStrategy = 'UNANIMOUS'; newPolicy.decisionStrategy = 'UNANIMOUS';
newPolicy.config = {}; newPolicy.config = {};
newPolicy.config.resources = ''; newPolicy.config.resources = '';
var scopeId = $location.search()['scpid'];
if (scopeId) {
newPolicy.config.scopes = [scopeId];
}
}, },
onCreate : function() { onCreate : function() {
@ -712,7 +728,7 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route,
}, realm, client, $scope); }, 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({ PolicyController.onInit({
getPolicyType : function() { getPolicyType : function() {
return "role"; return "role";
@ -723,6 +739,10 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.roles = data; $scope.roles = data;
}); });
Client.query({realm: $route.current.params.realm}, function (data) {
$scope.clients = data;
});
$scope.selectedRoles = []; $scope.selectedRoles = [];
$scope.selectRole = function(role) { $scope.selectRole = function(role) {
@ -732,10 +752,55 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.selectedRole = {}; $scope.selectedRole = {};
$scope.selectedRoles.push(role); $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) { $scope.removeFromList = function(role) {
list.splice(index, 1); 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); var roles = eval(policy.config.roles);
for (i = 0; i < roles.length; i++) { 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); selectedRoles.push(data);
$scope.selectedRoles = angular.copy(selectedRoles); $scope.selectedRoles = angular.copy(selectedRoles);
}); });
@ -764,7 +840,12 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = []; var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) { 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); $scope.policy.config.roles = JSON.stringify(roles);
@ -774,12 +855,35 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = []; var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) { 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); $scope.policy.config.roles = JSON.stringify(roles);
} }
}, realm, client, $scope); }, 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) { 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> <kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group clearfix"> <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}}..." <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"> ng-options="role as role.name for role in roles" data-ng-required="selectedUsers.length == 0 && selectedRoles.length == 0">
</select> </select>
</div> </div>
<kc-tooltip>{{:: 'authz-policy-role-roles.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'authz-policy-role-realm-roles.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group clearfix" style="margin-top: -15px;"> <div class="form-group clearfix" style="margin-top: -15px;">
<label class="col-md-2 control-label"></label> <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"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr data-ng-hide="!selectedRoles.length"> <tr>
<th>{{:: 'name' | translate}}</th> <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'" 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(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> <th>{{:: 'actions' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <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>{{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"> <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> </td>
</tr> </tr>
<tr data-ng-show="!selectedRoles.length"> <tr data-ng-show="!selectedRoles.length">
@ -98,7 +152,6 @@
</div> </div>
<input type="hidden" data-ng-model="policy.type"/> <input type="hidden" data-ng-model="policy.type"/>
</fieldset> </fieldset>
<div class="form-group" data-ng-show="access.manageAuthorization"> <div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2"> <div class="col-md-10 col-md-offset-2">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button> <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>

View file

@ -43,14 +43,14 @@
<ul> <ul>
<li data-ng-repeat="policyResult in result.policies"> <li data-ng-repeat="policyResult in result.policies">
<strong><a <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> 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> <span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision.</a> by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision.</a>
<ul> <ul>
<li data-ng-repeat="subPolicy in policyResult.associatedPolicies"> <li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
<strong><a <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" voted to <span style="color: green"
data-ng-show="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span> 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> <span style="color: red" data-ng-hide="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>.</a>

View file

@ -24,11 +24,33 @@
</tr> </tr>
<tr data-ng-hide="scopes.length == 0"> <tr data-ng-hide="scopes.length == 0">
<th>{{:: 'name' | translate}}</th> <th>{{:: 'name' | translate}}</th>
<th>{{:: 'authz-resources' | translate}}</th>
<th>{{:: 'authz-permissions' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="scope in scopes | filter:search | orderBy:'name'"> <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><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>
<tr data-ng-show="(scopes | filter:search).length == 0"> <tr data-ng-show="(scopes | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td> <td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>