keycloak-memoizeit
Changes
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java 39(+30 -9)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java 28(+19 -9)
core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java 3(+3 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 8(+1 -7)
model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java 7(+4 -3)
model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java 22(+18 -4)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java 12(+8 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java 17(+14 -3)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html 2(+1 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js 7(+4 -3)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html 24(+12 -12)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html 14(+7 -7)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 8(+1 -7)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java 15(+13 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java 118(+114 -4)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html 71(+62 -9)
Details
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
index af5f322..c8a99fe 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
@@ -23,9 +23,12 @@ import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import java.util.Map;
+
import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles;
/**
@@ -47,23 +50,41 @@ public class RolePolicyProvider implements PolicyProvider {
@Override
public void evaluate(Evaluation evaluation) {
- EvaluationContext context = evaluation.getContext();
- String[] roleIds = getRoles(this.policy);
+ Map<String, Object>[] roleIds = getRoles(this.policy);
if (roleIds.length > 0) {
- Identity identity = context.getIdentity();
+ Identity identity = evaluation.getContext().getIdentity();
+
+ for (Map<String, Object> current : roleIds) {
+ RoleModel role = getCurrentRealm().getRoleById((String) current.get("id"));
- for (String roleId : roleIds) {
- RoleModel role = getCurrentRealm().getRoleById(roleId);
+ if (role != null) {
+ boolean hasRole = hasRole(identity, role);
- if (role != null && identity.hasRole(role.getName())) {
- evaluation.grant();
- 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() {
return this.authorization.getKeycloakSession().getContext().getRealm();
}
@@ -72,4 +93,4 @@ public class RolePolicyProvider implements PolicyProvider {
public void close() {
}
-}
+}
\ No newline at end of file
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
index 598a41e..301bb7b 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
@@ -34,7 +34,9 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -81,11 +83,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
RoleModel removedRole = ((RoleRemovedEvent) event).getRole();
policyStore.findByType(getId()).forEach(policy -> {
- List<String> roles = new ArrayList<>();
-
- for (String roleId : getRoles(policy)) {
- if (!roleId.equals(removedRole.getId())) {
- roles.add(roleId);
+ List<Map> roles = new ArrayList<>();
+
+ for (Map<String,Object> role : getRoles(policy)) {
+ if (!role.get("id").equals(removedRole.getId())) {
+ Map updated = new HashMap();
+ updated.put("id", role.get("id"));
+ Object required = role.get("required");
+ if (required != null) {
+ updated.put("required", required);
+ }
+ roles.add(updated);
}
}
@@ -96,7 +104,9 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
});
policyStore.delete(policy.getId());
} else {
- policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
+ Map<String, String> config = policy.getConfig();
+ config.put("roles", JsonSerialization.writeValueAsString(roles));
+ policy.setConfig(config);
}
} catch (IOException e) {
throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e);
@@ -116,17 +126,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
return "role";
}
- static String[] getRoles(Policy policy) {
+ static Map<String, Object>[] getRoles(Policy policy) {
String roles = policy.getConfig().get("roles");
if (roles != null) {
try {
- return JsonSerialization.readValue(roles.getBytes(), String[].class);
+ return JsonSerialization.readValue(roles.getBytes(), Map[].class);
} catch (IOException e) {
throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e);
}
}
- return new String[]{};
+ return new Map[] {};
}
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
index 6ccac33..2776b0a 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.representations.idm.authorization;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URI;
@@ -39,12 +40,14 @@ public class ResourceRepresentation {
private String name;
private String uri;
private String type;
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<ScopeRepresentation> scopes;
@JsonProperty("icon_uri")
private String iconUri;
private ResourceOwnerRepresentation owner;
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PolicyRepresentation> policies;
/**
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
index 39aa9c7..3a1f252 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
@@ -34,6 +34,7 @@ public class ScopeRepresentation {
private String name;
private String iconUri;
private List<PolicyRepresentation> policies;
+ private List<ResourceRepresentation> resources;
/**
* Creates an instance.
@@ -94,6 +95,14 @@ public class ScopeRepresentation {
this.policies = policies;
}
+ public List<ResourceRepresentation> getResources() {
+ return this.resources;
+ }
+
+ public void setResources(List<ResourceRepresentation> resources) {
+ this.resources = resources;
+ }
+
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
index e9b470a..9614ca3 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
@@ -32,6 +32,8 @@ public class RoleRepresentation {
protected Boolean scopeParamRequired;
protected boolean composite;
protected Composites composites;
+ private Boolean clientRole;
+ private String containerId;
public static class Composites {
protected Set<String> realm;
@@ -122,4 +124,20 @@ public class RoleRepresentation {
public void setComposite(boolean composite) {
this.composite = composite;
}
+
+ public Boolean getClientRole() {
+ return clientRole;
+ }
+
+ public void setClientRole(Boolean clientRole) {
+ this.clientRole = clientRole;
+ }
+
+ public String getContainerId() {
+ return containerId;
+ }
+
+ public void setContainerId(String containerId) {
+ this.containerId = containerId;
+ }
}
diff --git a/examples/authz/hello-world-authz-service/hello-world-authz-service.json b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
index ea56e62..6e509c7 100644
--- a/examples/authz/hello-world-authz-service/hello-world-authz-service.json
+++ b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
@@ -1,4 +1,6 @@
{
+ "allowRemoteResourceManagement": false,
+ "policyEnforcementMode": "ENFORCING",
"resources": [
{
"name": "Default Resource",
@@ -8,22 +10,26 @@
],
"policies": [
{
- "name": "Only From Realm Policy",
+ "name": "Default Policy",
"description": "A policy that grants access only for users within this realm",
"type": "js",
+ "logic": "POSITIVE",
+ "decisionStrategy": "AFFIRMATIVE",
"config": {
"applyPolicies": "[]",
- "code": "var context = $evaluation.getContext();\n\n// using attributes from the evaluation context to obtain the realm\nvar contextAttributes = context.getAttributes();\nvar realmName = contextAttributes.getValue('kc.realm.name').asString(0);\n\n// using attributes from the identity to obtain the issuer\nvar identity = context.getIdentity();\nvar identityAttributes = identity.getAttributes();\nvar issuer = identityAttributes.getValue('iss').asString(0);\n\n// only users from the realm have access granted \nif (issuer.endsWith(realmName)) {\n $evaluation.grant();\n}"
+ "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
}
},
{
"name": "Default Permission",
"description": "A permission that applies to the default resource type",
"type": "resource",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"defaultResourceType": "urn:hello-world-authz-service:resources:default",
"default": "true",
- "applyPolicies": "[\"Only From Realm Policy\"]"
+ "applyPolicies": "[\"Default Policy\"]"
}
}
]
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
index 98f0856..203b6e2 100755
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
@@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl">
-<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
+<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
<div id="content-area" class="col-md-9" role="main">
<div id="content" ng-view/>
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 2990675..e58c5f5 100755
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
}
+
+ $scope.Identity = Identity;
});
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
$scope.profile = Profile.get();
});
-module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
+module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
$scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data;
});
$scope.deleteAlbum = function (album) {
- var newAlbum = new Album(album);
- newAlbum.$delete({id: album.id}, function () {
+ new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
index da78224..c0cc6e1 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
@@ -10,7 +10,7 @@
<td>
<ul>
<li data-ng-repeat="p in value">
- <a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
+ <a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
</li>
</ul>
</td>
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index bd5853e..78c252a 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -1,4 +1,4 @@
-<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
+<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/>
<br/>
@@ -15,7 +15,7 @@
</thead>
<tbody>
<tr data-ng-repeat="p in albums">
- <td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
+ <td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
</tr>
</tbody>
</table>
diff --git a/examples/authz/photoz/photoz-realm.json b/examples/authz/photoz/photoz-realm.json
index b3b2b81..baa8f66 100644
--- a/examples/authz/photoz/photoz-realm.json
+++ b/examples/authz/photoz/photoz-realm.json
@@ -53,7 +53,7 @@
}
],
"realmRoles": [
- "user", "admin", "uma_authorization"
+ "admin", "uma_authorization"
],
"clientRoles": {
"realm-management": [
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 388c9e4..c28bca3 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -109,13 +109,7 @@ public class AlbumService {
private void createProtectedResource(Album album) {
try {
- HashSet<ScopeRepresentation> scopes = new HashSet<>();
-
- scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
- scopes.add(new ScopeRepresentation(SCOPE_ALBUM_CREATE));
- scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
-
- ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
+ ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
albumResource.setOwner(album.getUserId());
diff --git a/examples/authz/photoz/photoz-restful-api-authz-service.json b/examples/authz/photoz/photoz-restful-api-authz-service.json
index e8d8862..6c786e7 100644
--- a/examples/authz/photoz/photoz-restful-api-authz-service.json
+++ b/examples/authz/photoz/photoz-restful-api-authz-service.json
@@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view"
},
{
- "name": "urn:photoz.com:scopes:album:create"
+ "name": "urn:photoz.com:scopes:album:delete"
},
{
- "name": "urn:photoz.com:scopes:album:delete"
+ "name": "urn:photoz.com:scopes:album:create"
}
]
},
@@ -44,12 +44,15 @@
"name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something",
"type": "drools",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession",
"mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy",
+ "applyPolicies": "[]",
"scannerPeriod": "1",
"scannerPeriodUnit": "Hours"
}
@@ -58,16 +61,22 @@
"name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something",
"type": "role",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
- "roles": "[\"admin\"]"
+ "applyPolicies": "[]",
+ "roles": "[{\"id\":\"admin\",\"required\":true}]"
}
},
{
"name": "Any User Policy",
"description": "Defines that any user can do something",
"type": "role",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
- "roles": "[\"user\"]"
+ "applyPolicies": "[]",
+ "roles": "[{\"id\":\"user\"}]"
}
},
{
@@ -77,6 +86,7 @@
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
+ "applyPolicies": "[]",
"code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n $evaluation.grant();\n}"
}
},
@@ -84,6 +94,8 @@
"name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
}
@@ -92,35 +104,28 @@
"name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate",
+ "logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
- "applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
+ "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
}
},
{
"name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org",
"type": "js",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
}
},
{
- "name": "Only in the Period",
- "description": "Access granted only during the morning",
- "type": "time",
- "config": {
- "noa": "2016-01-03 23:59:59",
- "expirationUnit": "Minutes",
- "nbf": "2016-01-01 00:00:00",
- "expirationTime": "1"
- }
- },
- {
"name": "Album Resource Permission",
"description": "General policies that apply to all album resources.",
"type": "resource",
+ "logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"defaultResourceType": "http://photoz.com/album",
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
index 6bdf96f..6e3271b 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
@@ -38,6 +38,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -99,7 +100,7 @@ public class CachedPolicyStore implements PolicyStore {
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
- return getDelegate().findByResourceServer(resourceServerId);
+ return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
@@ -179,12 +180,12 @@ public class CachedPolicyStore implements PolicyStore {
@Override
public List<Policy> findByType(String type) {
- return getDelegate().findByType(type);
+ return getDelegate().findByType(type).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
public List<Policy> findDependentPolicies(String id) {
- return getDelegate().findDependentPolicies(id);
+ return getDelegate().findDependentPolicies(id).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
private String getCacheKeyForPolicy(String policyId) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
index ee638f6..495df61 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
@@ -68,7 +68,21 @@ public class CachedResourceStore implements ResourceStore {
@Override
public void delete(String id) {
- this.cache.remove(getCacheKeyForResource(id));
+ List<CachedResource> removed = this.cache.remove(getCacheKeyForResource(id));
+
+ if (removed != null) {
+ CachedResource cachedResource = removed.get(0);
+ List<String> byOwner = this.cache.get(getResourceOwnerCacheKey(cachedResource.getOwner()));
+
+ if (byOwner != null) {
+ byOwner.remove(id);
+
+ if (byOwner.isEmpty()) {
+ this.cache.remove(getResourceOwnerCacheKey(cachedResource.getOwner()));
+ }
+ }
+ }
+
getDelegate().delete(id);
}
@@ -109,12 +123,12 @@ public class CachedResourceStore implements ResourceStore {
@Override
public List<Resource> findByResourceServer(String resourceServerId) {
- return getDelegate().findByResourceServer(resourceServerId);
+ return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
}
@Override
public List<Resource> findByScope(String... id) {
- return getDelegate().findByScope(id);
+ return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
}
@Override
@@ -283,7 +297,7 @@ public class CachedResourceStore implements ResourceStore {
return;
}
cached = new ArrayList<>();
- this.cache.put(getResourceOwnerCacheKey(resource.getOwner()), cached);
+ this.cache.put(cacheKey, cached);
}
if (cached != null && !cached.contains(resource.getId())) {
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
index 802989c..986d007 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -98,7 +98,7 @@ public class JPAResourceStore implements ResourceStore {
@Override
public List<Resource> findByScope(String... id) {
- Query query = entityManager.createQuery("from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
+ Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
query.setParameter("scopeIds", Arrays.asList(id));
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index ce2bc51..a70d3c0 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -95,7 +95,7 @@ public interface Attributes {
private final String[] values;
private final String name;
- Entry(String name, Collection<String> values) {
+ public Entry(String name, Collection<String> values) {
this.name = name;
this.values = values.toArray(new String[values.size()]);
}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
index ebc9679..f16e6c3 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
@@ -19,6 +19,10 @@ package org.keycloak.authorization.identity;
import org.keycloak.authorization.attribute.Attributes;
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Predicate;
+
/**
* <p>Represents a security identity, which can be a person or non-person entity that was previously authenticated.
*
@@ -45,13 +49,53 @@ public interface Identity {
Attributes getAttributes();
/**
- * Indicates if this identity is granted with a role with the given <code>roleName</code>.
+ * Indicates if this identity is granted with a role (realm or client) with the given <code>roleName</code>.
*
* @param roleName the name of the role
*
* @return true if the identity has the given role. Otherwise, it returns false.
*/
default boolean hasRole(String roleName) {
- return getAttributes().containsValue("roles", roleName);
+ return hasRealmRole(roleName) || hasClientRole(roleName);
+ }
+
+ /**
+ * Indicates if this identity is granted with a realm role with the given <code>roleName</code>.
+ *
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasRealmRole(String roleName) {
+ return getAttributes().containsValue("kc.realm.roles", roleName);
+ }
+
+ /**
+ * Indicates if this identity is granted with a client role with the given <code>roleName</code>.
+ *
+ * @param clientId the client id
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasClientRole(String clientId, String roleName) {
+ return getAttributes().containsValue("kc.client." + clientId + ".roles", roleName);
+ }
+
+ /**
+ * Indicates if this identity is granted with a client role with the given <code>roleName</code>.
+ *
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasClientRole(String roleName) {
+ return getAttributes().toMap().entrySet().stream().filter(entry -> {
+ String key = entry.getKey();
+ if (key.startsWith("kc.client") && key.endsWith(".roles")) {
+ return getAttributes().containsValue(key, roleName);
+ }
+ return false;
+ }).findFirst().isPresent();
}
}
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
index e5230f9..8ff3664 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
@@ -17,11 +17,22 @@
package org.keycloak.migration.migrators;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.util.JsonSerialization;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
*
@@ -33,6 +44,7 @@ public class MigrateTo2_1_0 {
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateDefaultRequiredAction(realm);
+ migrateRolePolicies(realm, session);
}
}
@@ -46,4 +58,46 @@ public class MigrateTo2_1_0 {
otpAction.setName("Configure OTP");
}
+
+ // KEYCLOAK-3338: Changes to how role policy config is stored"
+ private void migrateRolePolicies(RealmModel realm, KeycloakSession session) {
+ AuthorizationProvider authorizationProvider = session.getProvider(AuthorizationProvider.class);
+ StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+ realm.getClients().forEach(clientModel -> {
+ ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getId());
+
+ if (resourceServer != null) {
+ policyStore.findByType("role").forEach(policy -> {
+ Map<String, String> config = policy.getConfig();
+ String roles = config.get("roles");
+ List roleConfig;
+
+ try {
+ roleConfig = JsonSerialization.readValue(roles, List.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Malformed configuration for role policy [" + policy.getName() + "].", e);
+ }
+
+ if (!roleConfig.isEmpty() && roleConfig.get(0) instanceof String) {
+ try {
+ config.put("roles", JsonSerialization.writeValueAsString(roleConfig.stream().map(new Function<String, Map>() {
+ @Override
+ public Map apply(String roleId) {
+ Map updated = new HashMap();
+
+ updated.put("id", roleId);
+
+ return updated;
+ }
+ }).collect(Collectors.toList())));
+ policy.setConfig(config);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to migrate role policy [" + policy.getName() + "].", e);
+ }
+ }
+ });
+ }
+ });
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 7f7f7e0..203736f 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -218,6 +218,8 @@ public class ModelToRepresentation {
rep.setDescription(role.getDescription());
rep.setScopeParamRequired(role.isScopeParamRequired());
rep.setComposite(role.isComposite());
+ rep.setClientRole(role.isClientRole());
+ rep.setContainerId(role.getContainerId());
return rep;
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index f4d8feb..8e4ec5e 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -136,11 +136,19 @@ public class PolicyEvaluationService {
}
private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) {
- if (representation.isEntitlements()) {
+ List<PolicyEvaluationRequest.Resource> resources = representation.getResources();
+
+ for (PolicyEvaluationRequest.Resource resource : new ArrayList<>(resources)) {
+ if (resource.getId() == null && (resource.getScopes() == null || resource.getScopes().isEmpty())) {
+ resources.remove(resource);
+ }
+ }
+
+ if (representation.isEntitlements() || resources.isEmpty()) {
return Permissions.all(this.resourceServer, evaluationContext.getIdentity(), authorization);
}
- return representation.getResources().stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
+ return resources.stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
Set<String> givenScopes = resource.getScopes();
if (givenScopes == null) {
@@ -157,7 +165,13 @@ public class PolicyEvaluationService {
} else if (resource.getType() != null) {
return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer));
} else {
- return scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer));
+ List<ResourcePermission> collect = scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList());
+
+ for (Scope scope : scopes) {
+ collect.addAll(storeFactory.getResourceStore().findByScope(scope.getId()).stream().map(resource12 -> new ResourcePermission(resource12, asList(scope), resourceServer)).collect(Collectors.toList()));
+ }
+
+ return collect.stream();
}
}).collect(Collectors.toList());
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
index d2d623c..7c529e9 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -154,7 +154,11 @@ public class PolicyService {
}
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());
@@ -271,7 +275,6 @@ public class PolicyService {
}
StoreFactory storeFactory = authorization.getStoreFactory();
- PolicyStore policyStore = storeFactory.getPolicyStore();
for (String scopeId : scopeIds) {
boolean hasScope = false;
diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
index 77fe01e..9a3c9b3 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
@@ -21,14 +21,10 @@ package org.keycloak.authorization.admin.representation;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.admin.util.Models;
-import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.policy.evaluation.Result.PolicyResult;
-import org.keycloak.authorization.store.StoreFactory;
-import org.keycloak.authorization.util.Permissions;
-import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@@ -90,9 +86,17 @@ public class PolicyEvaluationResponse {
policies.add(toRepresentation(policy, authorization));
}
+ if (rep.getResource().getId() != null) {
+ if (!rep.getScopes().isEmpty()) {
+ rep.getResource().setName(rep.getResource().getName() + " with scopes " + rep.getScopes().stream().map(ScopeRepresentation::getName).collect(Collectors.toList()));
+ }
+ }
+
rep.setPolicies(policies);
}
+ resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName()));
+
response.results = resultsRep;
return response;
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index 59ba844..93c5ce8 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -17,8 +17,6 @@
*/
package org.keycloak.authorization.admin;
-import org.jboss.resteasy.plugins.providers.multipart.InputPart;
-import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.util.Models;
@@ -146,7 +144,11 @@ public class ResourceServerService {
.stream().map(resource -> {
ResourceRepresentation rep = Models.toRepresentation(resource, resourceServer, authorization);
- rep.getOwner().setId(null);
+ if (rep.getOwner().getId().equals(this.resourceServer.getClientId())) {
+ rep.setOwner(null);
+ } else {
+ rep.getOwner().setId(null);
+ }
rep.setId(null);
rep.setPolicies(null);
rep.getScopes().forEach(scopeRepresentation -> {
@@ -175,15 +177,8 @@ public class ResourceServerService {
ScopeRepresentation rep = Models.toRepresentation(scope, authorization);
rep.setId(null);
-
- rep.getPolicies().forEach(policyRepresentation -> {
- policyRepresentation.setId(null);
- policyRepresentation.setConfig(null);
- policyRepresentation.setType(null);
- policyRepresentation.setDecisionStrategy(null);
- policyRepresentation.setDescription(null);
- policyRepresentation.setDependentPolicies(null);
- });
+ rep.setPolicies(null);
+ rep.setResources(null);
return rep;
}).collect(Collectors.toList());
@@ -258,131 +253,74 @@ public class ResourceServerService {
String roles = config.get("roles");
if (roles != null && !roles.isEmpty()) {
- roles = roles.replace("[", "");
- roles = roles.replace("]", "");
-
- if (!roles.isEmpty()) {
- String roleNames = "";
-
- for (String role : roles.split(",")) {
- if (!roleNames.isEmpty()) {
- roleNames = roleNames + ",";
- }
-
- role = role.replace("\"", "");
-
- roleNames = roleNames + "\"" + this.realm.getRole(role).getId() + "\"";
- }
-
- config.put("roles", "[" + roleNames + "]");
+ try {
+ List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
+ config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> {
+ roleConfig.put("id", realm.getRole(roleConfig.get("id").toString()).getId());
+ return roleConfig;
+ }).collect(Collectors.toList())));
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String users = config.get("users");
- if (users != null) {
- users = users.replace("[", "");
- users = users.replace("]", "");
-
- if (!users.isEmpty()) {
- String userNames = "";
-
- for (String user : users.split(",")) {
- if (!userNames.isEmpty()) {
- userNames = userNames + ",";
- }
-
- user = user.replace("\"", "");
-
- userNames = userNames + "\"" + this.session.users().getUserByUsername(user, this.realm).getId() + "\"";
- }
-
- config.put("users", "[" + userNames + "]");
+ if (users != null && !users.isEmpty()) {
+ try {
+ List<String> usersMap = JsonSerialization.readValue(users, List.class);
+ config.put("users", JsonSerialization.writeValueAsString(usersMap.stream().map(userName -> this.session.users().getUserByUsername(userName, this.realm).getId()).collect(Collectors.toList())));
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String scopes = config.get("scopes");
if (scopes != null && !scopes.isEmpty()) {
- scopes = scopes.replace("[", "");
- scopes = scopes.replace("]", "");
-
- if (!scopes.isEmpty()) {
- String scopeNames = "";
-
- for (String scope : scopes.split(",")) {
- if (!scopeNames.isEmpty()) {
- scopeNames = scopeNames + ",";
- }
-
- scope = scope.replace("\"", "");
-
- Scope newScope = scopeStore.findByName(scope, resourceServer.getId());
+ try {
+ List<String> scopesMap = JsonSerialization.readValue(scopes, List.class);
+ config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> {
+ Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId());
if (newScope == null) {
- throw new RuntimeException("Scope with name [" + scope + "] not defined.");
+ throw new RuntimeException("Scope with name [" + scopeName + "] not defined.");
}
- scopeNames = scopeNames + "\"" + newScope.getId() + "\"";
- }
-
- config.put("scopes", "[" + scopeNames + "]");
+ return newScope.getId();
+ }).collect(Collectors.toList())));
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String policyResources = config.get("resources");
if (policyResources != null && !policyResources.isEmpty()) {
- policyResources = policyResources.replace("[", "");
- policyResources = policyResources.replace("]", "");
-
- if (!policyResources.isEmpty()) {
- String resourceNames = "";
-
- for (String resource : policyResources.split(",")) {
- if (!resourceNames.isEmpty()) {
- resourceNames = resourceNames + ",";
- }
-
- resource = resource.replace("\"", "");
-
- if ("".equals(resource)) {
- continue;
- }
-
- resourceNames = resourceNames + "\"" + storeFactory.getResourceStore().findByName(resource, resourceServer.getId()).getId() + "\"";
- }
-
- config.put("resources", "[" + resourceNames + "]");
+ try {
+ List<String> resources = JsonSerialization.readValue(policyResources, List.class);
+ config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> storeFactory.getResourceStore().findByName(resourceName, resourceServer.getId()).getId()).collect(Collectors.toList())));
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
String applyPolicies = config.get("applyPolicies");
if (applyPolicies != null && !applyPolicies.isEmpty()) {
- applyPolicies = applyPolicies.replace("[", "");
- applyPolicies = applyPolicies.replace("]", "");
-
- if (!applyPolicies.isEmpty()) {
- String policyNames = "";
-
- for (String pId : applyPolicies.split(",")) {
- if (!policyNames.isEmpty()) {
- policyNames = policyNames + ",";
- }
-
- pId = pId.replace("\"", "").trim();
-
- Policy policy = policyStore.findByName(pId, resourceServer.getId());
+ try {
+ List<String> policies = JsonSerialization.readValue(applyPolicies, List.class);
+ config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> {
+ Policy policy = policyStore.findByName(policyName, resourceServer.getId());
if (policy == null) {
- throw new RuntimeException("Policy with name [" + pId + "] not defined.");
+ throw new RuntimeException("Policy with name [" + policyName + "] not defined.");
}
- policyNames = policyNames + "\"" + policy.getId() + "\"";
- }
-
- config.put("applyPolicies", "[" + policyNames + "]");
+ return policy.getId();
+ }).collect(Collectors.toList())));
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
}
}
@@ -491,123 +429,59 @@ public class ResourceServerService {
}
private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) {
- PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
-
- rep.setId(null);
- rep.setDependentPolicies(null);
-
- Map<String, String> config = rep.getConfig();
-
- String roles = config.get("roles");
+ try {
+ PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
- if (roles != null && !roles.isEmpty()) {
- roles = roles.replace("[", "");
- roles = roles.replace("]", "");
-
- if (!roles.isEmpty()) {
- String roleNames = "";
-
- for (String role : roles.split(",")) {
- if (!roleNames.isEmpty()) {
- roleNames = roleNames + ",";
- }
+ rep.setId(null);
+ rep.setDependentPolicies(null);
- role = role.replace("\"", "");
+ Map<String, String> config = rep.getConfig();
- roleNames = roleNames + "\"" + this.realm.getRoleById(role).getName() + "\"";
- }
+ String roles = config.get("roles");
- config.put("roles", "[" + roleNames + "]");
+ if (roles != null && !roles.isEmpty()) {
+ List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
+ config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
+ roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
+ return roleMap;
+ }).collect(Collectors.toList())));
}
- }
-
- String users = config.get("users");
- if (users != null) {
- users = users.replace("[", "");
- users = users.replace("]", "");
+ String users = config.get("users");
- if (!users.isEmpty()) {
+ if (users != null && !users.isEmpty()) {
UserFederationManager userManager = this.session.users();
- String userNames = "";
-
- for (String user : users.split(",")) {
- if (!userNames.isEmpty()) {
- userNames = userNames + ",";
- }
-
- user = user.replace("\"", "");
-
- userNames = userNames + "\"" + userManager.getUserById(user, this.realm).getUsername() + "\"";
- }
-
- config.put("users", "[" + userNames + "]");
+ List<String> userIds = JsonSerialization.readValue(users, List.class);
+ config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, this.realm).getUsername()).collect(Collectors.toList())));
}
- }
-
- String scopes = config.get("scopes");
- if (scopes != null && !scopes.isEmpty()) {
- scopes = scopes.replace("[", "");
- scopes = scopes.replace("]", "");
+ String scopes = config.get("scopes");
- if (!scopes.isEmpty()) {
+ if (scopes != null && !scopes.isEmpty()) {
ScopeStore scopeStore = storeFactory.getScopeStore();
- String scopeNames = "";
-
- for (String scope : scopes.split(",")) {
- if (!scopeNames.isEmpty()) {
- scopeNames = scopeNames + ",";
- }
-
- scope = scope.replace("\"", "");
-
- scopeNames = scopeNames + "\"" + scopeStore.findById(scope).getName() + "\"";
- }
-
- config.put("scopes", "[" + scopeNames + "]");
+ List<String> scopeIds = JsonSerialization.readValue(scopes, List.class);
+ config.put("scopes", JsonSerialization.writeValueAsString(scopeIds.stream().map(scopeId -> scopeStore.findById(scopeId).getName()).collect(Collectors.toList())));
}
- }
- String policyResources = config.get("resources");
-
- if (policyResources != null && !policyResources.isEmpty()) {
- policyResources = policyResources.replace("[", "");
- policyResources = policyResources.replace("]", "");
+ String policyResources = config.get("resources");
- if (!policyResources.isEmpty()) {
+ if (policyResources != null && !policyResources.isEmpty()) {
ResourceStore resourceStore = storeFactory.getResourceStore();
- String resourceNames = "";
-
- for (String resource : policyResources.split(",")) {
- if (!resourceNames.isEmpty()) {
- resourceNames = resourceNames + ",";
- }
-
- resource = resource.replace("\"", "");
-
- resourceNames = resourceNames + "\"" + resourceStore.findById(resource).getName() + "\"";
- }
-
- config.put("resources", "[" + 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())));
}
- }
- String policyNames = "";
- Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+ Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
- if (!associatedPolicies.isEmpty()) {
- for (Policy associatedPolicy : associatedPolicies) {
- if (!policyNames.isEmpty()) {
- policyNames = policyNames + ",";
- }
-
- policyNames = policyNames + "\"" + associatedPolicy.getName() + "\"";
+ if (!associatedPolicies.isEmpty()) {
+ config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList())));
}
- config.put("applyPolicies", "[" + policyNames + "]");
- }
+ rep.setAssociatedPolicies(null);
- return rep;
+ return rep;
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e);
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
index 60dc51e..7e9d66d 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
@@ -63,13 +63,18 @@ public final class Models {
scope.setId(model.getId());
scope.setName(model.getName());
scope.setIconUri(model.getIconUri());
- scope.setPolicies(new ArrayList<>());
- Set<Policy> policies = new HashSet<>();
+ StoreFactory storeFactory = authorizationProvider.getStoreFactory();
- policies.addAll(authorizationProvider.getStoreFactory().getPolicyStore().findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()));
+ scope.setResources(new ArrayList<>());
- for (Policy policyModel : policies) {
+ storeFactory.getResourceStore().findByScope(model.getId()).forEach(resource -> scope.getResources().add(toRepresentation(resource, resource.getResourceServer(), authorizationProvider)));
+
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+
+ scope.setPolicies(new ArrayList<>());
+
+ policyStore.findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()).forEach(policyModel -> {
PolicyRepresentation policy = new PolicyRepresentation();
policy.setId(policyModel.getId());
@@ -79,7 +84,7 @@ public final class Models {
if (!scope.getPolicies().contains(policy)) {
scope.getPolicies().add(policy);
}
- }
+ });
return scope;
}
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 5ddd431..c40c38a 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
+import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.util.JsonSerialization;
@@ -37,6 +38,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -53,61 +55,59 @@ public class KeycloakIdentity implements Identity {
}
public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
- this.accessToken = accessToken;
-
- if (this.accessToken == null) {
+ if (accessToken == null) {
throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
}
-
+ if (keycloakSession == null) {
+ throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN);
+ }
+ this.accessToken = accessToken;
this.keycloakSession = keycloakSession;
this.realm = keycloakSession.getContext().getRealm();
- HashMap<String, Collection<String>> attributes = new HashMap<>();
+ Map<String, Collection<String>> attributes = new HashMap<>();
try {
ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
Iterator<String> iterator = objectNode.fieldNames();
- List<String> roleNames = new ArrayList<>();
while (iterator.hasNext()) {
String fieldName = iterator.next();
JsonNode fieldValue = objectNode.get(fieldName);
List<String> values = new ArrayList<>();
- values.add(fieldValue.asText());
-
- if (fieldName.equals("realm_access")) {
- JsonNode grantedRoles = fieldValue.get("roles");
+ if (fieldValue.isArray()) {
+ Iterator<JsonNode> valueIterator = fieldValue.iterator();
- if (grantedRoles != null) {
- Iterator<JsonNode> rolesIt = grantedRoles.iterator();
+ while (valueIterator.hasNext()) {
+ values.add(valueIterator.next().asText());
+ }
+ } else {
+ String value = fieldValue.asText();
- while (rolesIt.hasNext()) {
- roleNames.add(rolesIt.next().asText());
- }
+ if (StringUtil.isNullOrEmpty(value)) {
+ continue;
}
+
+ values.add(value);
}
- if (fieldName.equals("resource_access")) {
- Iterator<JsonNode> resourceAccessIt = fieldValue.iterator();
+ if (!values.isEmpty()) {
+ attributes.put(fieldName, values);
+ }
+ }
- while (resourceAccessIt.hasNext()) {
- JsonNode grantedRoles = resourceAccessIt.next().get("roles");
+ AccessToken.Access realmAccess = accessToken.getRealmAccess();
- if (grantedRoles != null) {
- Iterator<JsonNode> rolesIt = grantedRoles.iterator();
+ if (realmAccess != null) {
+ attributes.put("kc.realm.roles", realmAccess.getRoles());
+ }
- while (rolesIt.hasNext()) {
- roleNames.add(rolesIt.next().asText());
- }
- }
- }
- }
+ Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
- attributes.put(fieldName, values);
+ if (resourceAccess != null) {
+ resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
}
-
- attributes.put("roles", roleNames);
} catch (Exception e) {
throw new RuntimeException("Error while reading attributes from security token.", e);
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index f3695d0..45fb6fe 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -28,6 +28,7 @@ import org.keycloak.authorization.protection.permission.PermissionService;
import org.keycloak.authorization.protection.permission.PermissionsService;
import org.keycloak.authorization.protection.resource.ResourceService;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ErrorResponseException;
@@ -48,16 +49,12 @@ public class ProtectionService {
@Path("/resource_set")
public Object resource() {
KeycloakIdentity identity = createIdentity();
-
- if (!identity.hasRole("uma_protection")) {
- throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
- }
-
- ResourceSetService resourceManager = new ResourceSetService(getResourceServer(identity), this.authorization, null);
+ ResourceServer resourceServer = getResourceServer(identity);
+ ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
- ResourceService resource = new ResourceService(getResourceServer(identity), identity, resourceManager, this.authorization);
+ ResourceService resource = new ResourceService(resourceServer, identity, resourceManager, this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -68,10 +65,6 @@ public class ProtectionService {
public Object permission() {
KeycloakIdentity identity = createIdentity();
- if (!identity.hasRole("uma_protection")) {
- throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
- }
-
PermissionService resource = new PermissionService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -83,10 +76,6 @@ public class ProtectionService {
public Object permissions() {
KeycloakIdentity identity = createIdentity();
- if (!identity.hasRole("uma_protection")) {
- throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
- }
-
PermissionsService resource = new PermissionsService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -95,7 +84,17 @@ public class ProtectionService {
}
private KeycloakIdentity createIdentity() {
- return new KeycloakIdentity(this.authorization.getKeycloakSession());
+ KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
+ ResourceServer resourceServer = getResourceServer(identity);
+ KeycloakSession keycloakSession = authorization.getKeycloakSession();
+ RealmModel realm = keycloakSession.getContext().getRealm();
+ ClientModel client = realm.getClientById(resourceServer.getClientId());
+
+ if (!identity.hasClientRole(client.getClientId(), "uma_protection")) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
+ }
+
+ return identity;
}
private ResourceServer getResourceServer(Identity identity) {
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index f2cce51..ed49697 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -61,20 +61,38 @@ public final class Permissions {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
- resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
- resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
+ resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
+ resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
return permissions;
}
- public static List<ResourcePermission> createResourcePermissions(Resource resource) {
+ public static List<ResourcePermission> createResourcePermissions(Resource resource, ResourceServer resourceServer, AuthorizationProvider authorization) {
List<ResourcePermission> permissions = new ArrayList<>();
List<Scope> scopes = resource.getScopes();
- permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
+ if (scopes.isEmpty()) {
+ String type = resource.getType();
+
+ // check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource
+ // is owned by the resource server itself
+ if (type != null) {
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ ResourceStore resourceStore = storeFactory.getResourceStore();
+ resourceStore.findByType(type).forEach(resource1 -> {
+ if (resource1.getOwner().equals(resourceServer.getClientId())) {
+ scopes.addAll(resource1.getScopes());
+ }
+ });
+ }
+ }
- for (Scope scope : scopes) {
- permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
+ 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;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java
index 31b221b..1faf21e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java
@@ -49,6 +49,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -56,6 +57,8 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import static org.jboss.aesh.terminal.Key.e;
+
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@@ -279,8 +282,12 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
RoleModel adminRole = realm.getRole("admin");
+ Map role = new HashMap();
+
+ role.put("id", adminRole.getId());
+
try {
- config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
+ config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -352,10 +359,14 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
Policy policy = policyStore.create("Any User Policy", "role", resourceServer);
HashedMap config = new HashedMap();
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
- RoleModel adminRole = realm.getRole("user");
+ RoleModel userRole = realm.getRole("user");
+
+ Map role = new HashMap();
+
+ role.put("id", userRole.getId());
try {
- config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
+ config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
index 5ac24ea..bec9fae 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
@@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl">
-<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
+<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
<div id="content-area" class="col-md-9" role="main">
<div id="content" ng-view/>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 2990675..e58c5f5 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
}
+
+ $scope.Identity = Identity;
});
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
$scope.profile = Profile.get();
});
-module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
+module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
$scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data;
});
$scope.deleteAlbum = function (album) {
- var newAlbum = new Album(album);
- newAlbum.$delete({id: album.id}, function () {
+ new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
index da78224..00f52d7 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
@@ -1,19 +1,19 @@
<h1>All Albums</h1>
<table class="table" data-ng-repeat="(key, value) in albums">
<thead>
- <tr>
- <th>{{key}}</th>
- </tr>
+ <tr>
+ <th>{{key}}</th>
+ </tr>
</thead>
<tbody>
- <tr>
- <td>
- <ul>
- <li data-ng-repeat="p in value">
- <a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
- </li>
- </ul>
- </td>
- </tr>
+ <tr>
+ <td>
+ <ul>
+ <li data-ng-repeat="p in value">
+ <a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
+ </li>
+ </ul>
+ </td>
+ </tr>
</tbody>
</table>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index bd5853e..e144d1b 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -1,4 +1,4 @@
-<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
+<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/>
<br/>
@@ -9,14 +9,14 @@
<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">
<thead>
- <tr>
- <th>Your Albums</th>
- </tr>
+ <tr>
+ <th>Your Albums</th>
+ </tr>
</thead>
<tbody>
- <tr data-ng-repeat="p in albums">
- <td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
- </tr>
+ <tr data-ng-repeat="p in albums">
+ <td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
+ </tr>
</tbody>
</table>
</div>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
index b3b2b81..baa8f66 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
@@ -53,7 +53,7 @@
}
],
"realmRoles": [
- "user", "admin", "uma_authorization"
+ "admin", "uma_authorization"
],
"clientRoles": {
"realm-management": [
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 388c9e4..c28bca3 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -109,13 +109,7 @@ public class AlbumService {
private void createProtectedResource(Album album) {
try {
- HashSet<ScopeRepresentation> scopes = new HashSet<>();
-
- scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
- scopes.add(new ScopeRepresentation(SCOPE_ALBUM_CREATE));
- scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
-
- ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
+ ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
albumResource.setOwner(album.getUserId());
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
index cc8c8f8..6c786e7 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
@@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view"
},
{
- "name": "urn:photoz.com:scopes:album:create"
+ "name": "urn:photoz.com:scopes:album:delete"
},
{
- "name": "urn:photoz.com:scopes:album:delete"
+ "name": "urn:photoz.com:scopes:album:create"
}
]
},
@@ -44,12 +44,15 @@
"name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something",
"type": "drools",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession",
- "mavenArtifactGroupId": "org.keycloak.testsuite",
+ "mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy",
+ "applyPolicies": "[]",
"scannerPeriod": "1",
"scannerPeriodUnit": "Hours"
}
@@ -58,16 +61,22 @@
"name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something",
"type": "role",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
- "roles": "[\"admin\"]"
+ "applyPolicies": "[]",
+ "roles": "[{\"id\":\"admin\",\"required\":true}]"
}
},
{
"name": "Any User Policy",
"description": "Defines that any user can do something",
"type": "role",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
- "roles": "[\"user\"]"
+ "applyPolicies": "[]",
+ "roles": "[{\"id\":\"user\"}]"
}
},
{
@@ -77,6 +86,7 @@
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
+ "applyPolicies": "[]",
"code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n $evaluation.grant();\n}"
}
},
@@ -84,6 +94,8 @@
"name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
}
@@ -92,35 +104,28 @@
"name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate",
+ "logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
- "applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
+ "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
}
},
{
"name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org",
"type": "js",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
}
},
{
- "name": "Only in the Period",
- "description": "Access granted only during the morning",
- "type": "time",
- "config": {
- "noa": "2016-01-03 23:59:59",
- "expirationUnit": "Minutes",
- "nbf": "2016-01-01 00:00:00",
- "expirationTime": "1"
- }
- },
- {
"name": "Album Resource Permission",
"description": "General policies that apply to all album resources.",
"type": "resource",
+ "logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"defaultResourceType": "http://photoz.com/album",
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index 09bf6a9..0c3b108 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -76,17 +76,28 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
pause(500);
}
- public void login(String username, String password) {
+ public void login(String username, String password) throws InterruptedException {
navigateTo();
-
+ Thread.sleep(2000);
if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) {
+ Thread.sleep(2000);
logOut();
+ navigateTo();
}
+ Thread.sleep(2000);
+
this.loginPage.form().login(username, password);
}
public boolean wasDenied() {
return this.driver.findElement(By.id("output")).getText().contains("You can not access");
}
+
+ public void viewAlbum(String name) {
+ By id = By.id("view-" + name);
+ WaitUtils.waitUntilElement(id);
+ this.driver.findElements(id).forEach(WebElement::click);
+ pause(500);
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 32f23be..59a7a31 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -16,12 +16,10 @@
*/
package org.keycloak.testsuite.adapter.example.authorization;
-import org.apache.commons.io.IOUtils;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
-import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
@@ -39,7 +37,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.junit.Assert.assertFalse;
@@ -144,7 +141,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
- this.clientPage.login("admin", "admin");
this.clientPage.navigateToAdminAlbum();
this.clientPage.deleteAlbum("Alice-Family-Album");
@@ -186,10 +182,124 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
+ this.clientPage.navigateToAdminAlbum();
+
+ assertTrue(this.clientPage.wasDenied());
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ @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);
}
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index fbfe64b..50d5919 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -953,6 +953,7 @@ authz-no-resources=No resources
authz-result=Result
authz-authorization-services-enabled=Authorization Enabled
authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client
+authz-required=Required
# Authz Settings
authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server.
@@ -1016,7 +1017,9 @@ authz-select-a-policy=Select a policy
# Authz Role Policy Detail
authz-add-role-policy=Add Role Policy
authz-no-roles-assigned=No roles assigned.
-authz-policy-role-roles.tooltip=Specifies which role(s) are allowed by this policy.
+authz-policy-role-realm-roles.tooltip=Specifies which *realm* role(s) are allowed by this policy.
+authz-policy-role-clients.tooltip=Selects a client in order to filter the client roles that can be applied to this policy.
+authz-policy-role-client-roles.tooltip=Specifies which *client* role(s) are allowed by this policy.
# Authz User Policy Detail
authz-add-user-policy=Add User Policy
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
index 680cb5b..bbe0fd0 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
@@ -150,6 +150,14 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
client : client.id,
rsrid : $route.current.params.rsrid,
}, function(data) {
+ if (!data.scopes) {
+ data.scopes = [];
+ }
+
+ if (!data.policies) {
+ data.policies = [];
+ }
+
$scope.resource = angular.copy(data);
$scope.changed = false;
@@ -157,9 +165,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
$scope.resource.scopes[i] = $scope.resource.scopes[i].name;
}
- data = angular.copy($scope.resource);
-
- $scope.originalResource = data;
+ $scope.originalResource = angular.copy($scope.resource);
$scope.$watch('resource', function() {
if (!angular.equals($scope.resource, data)) {
@@ -237,6 +243,10 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo
ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) {
$scope.scopes = data;
});
+
+ $scope.createPolicy = function(scope) {
+ $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id});
+ }
});
});
@@ -534,7 +544,7 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro
}, realm, client, $scope);
});
-module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
+module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, $location, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
PolicyController.onInit({
getPolicyType : function() {
return "scope";
@@ -624,6 +634,12 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
newPolicy.decisionStrategy = 'UNANIMOUS';
newPolicy.config = {};
newPolicy.config.resources = '';
+
+ var scopeId = $location.search()['scpid'];
+
+ if (scopeId) {
+ newPolicy.config.scopes = [scopeId];
+ }
},
onCreate : function() {
@@ -712,7 +728,7 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route,
}, realm, client, $scope);
});
-module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, PolicyController, Role, RoleById) {
+module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, Client, ClientRole, PolicyController, Role, RoleById) {
PolicyController.onInit({
getPolicyType : function() {
return "role";
@@ -723,6 +739,10 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.roles = data;
});
+ Client.query({realm: $route.current.params.realm}, function (data) {
+ $scope.clients = data;
+ });
+
$scope.selectedRoles = [];
$scope.selectRole = function(role) {
@@ -732,10 +752,55 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.selectedRole = {};
$scope.selectedRoles.push(role);
+
+ var clientRoles = [];
+
+ if ($scope.clientRoles) {
+ for (i = 0; i < $scope.clientRoles.length; i++) {
+ if ($scope.clientRoles[i].id != role.id) {
+ clientRoles.push($scope.clientRoles[i]);
+ }
+ }
+ $scope.clientRoles = clientRoles;
+ }
}
- $scope.removeFromList = function(list, index) {
- list.splice(index, 1);
+ $scope.removeFromList = function(role) {
+ if ($scope.clientRoles && $scope.selectedClient && $scope.selectedClient.id == role.containerId) {
+ $scope.clientRoles.push(role);
+ }
+ var index = $scope.selectedRoles.indexOf(role);
+ if (index != -1) {
+ $scope.selectedRoles.splice(index, 1);
+ }
+ }
+
+ $scope.selectClient = function() {
+ if (!$scope.selectedClient) {
+ $scope.clientRoles = [];
+ return;
+ }
+ ClientRole.query({realm: $route.current.params.realm, client: $scope.selectedClient.id}, function(data) {
+ var roles = [];
+
+ for (j = 0; j < data.length; j++) {
+ var defined = false;
+
+ for (i = 0; i < $scope.selectedRoles.length; i++) {
+ if ($scope.selectedRoles[i].id == data[j].id) {
+ defined = true;
+ break;
+ }
+ }
+
+ if (!defined) {
+ data[j].container = {};
+ data[j].container.name = $scope.selectedClient.clientId;
+ roles.push(data[j]);
+ }
+ }
+ $scope.clientRoles = roles;
+ });
}
},
@@ -746,7 +811,18 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = eval(policy.config.roles);
for (i = 0; i < roles.length; i++) {
- RoleById.get({realm: $route.current.params.realm, role: roles[i]}, function(data) {
+ RoleById.get({realm: $route.current.params.realm, role: roles[i].id}, function(data) {
+ for (i = 0; i < roles.length; i++) {
+ if (roles[i].id == data.id) {
+ data.required = roles[i].required ? true : false;
+ }
+ }
+ for (i = 0; i < $scope.clients.length; i++) {
+ if ($scope.clients[i].id == data.containerId) {
+ data.container = {};
+ data.container.name = $scope.clients[i].clientId;
+ }
+ }
selectedRoles.push(data);
$scope.selectedRoles = angular.copy(selectedRoles);
});
@@ -764,7 +840,12 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) {
- roles.push($scope.selectedRoles[i].id);
+ var role = {};
+ role.id = $scope.selectedRoles[i].id;
+ if ($scope.selectedRoles[i].required) {
+ role.required = $scope.selectedRoles[i].required;
+ }
+ roles.push(role);
}
$scope.policy.config.roles = JSON.stringify(roles);
@@ -774,12 +855,35 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) {
- roles.push($scope.selectedRoles[i].id);
+ var role = {};
+ role.id = $scope.selectedRoles[i].id;
+ if ($scope.selectedRoles[i].required) {
+ role.required = $scope.selectedRoles[i].required;
+ }
+ roles.push(role);
}
$scope.policy.config.roles = JSON.stringify(roles);
}
}, realm, client, $scope);
+
+ $scope.hasRealmRole = function () {
+ for (i = 0; i < $scope.selectedRoles.length; i++) {
+ if (!$scope.selectedRoles[i].clientRole) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ $scope.hasClientRole = function () {
+ for (i = 0; i < $scope.selectedRoles.length; i++) {
+ if ($scope.selectedRoles[i].clientRole) {
+ return true;
+ }
+ }
+ return false;
+ }
});
module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
index 9b0c199..8904bb3 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
@@ -49,31 +49,85 @@
<kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
- <label class="col-md-2 control-label" for="roles">{{:: 'roles' | translate}} <span class="required">*</span></label>
+ <label class="col-md-2 control-label" for="roles">{{:: 'realm-roles' | translate}} <span class="required">*</span></label>
- <div class="col-md-6">
+ <div class="col-md-4">
<select ui-select2="{ minimumInputLength: 1}" id="roles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
ng-options="role as role.name for role in roles" data-ng-required="selectedUsers.length == 0 && selectedRoles.length == 0">
</select>
</div>
- <kc-tooltip>{{:: 'authz-policy-role-roles.tooltip' | translate}}</kc-tooltip>
+ <kc-tooltip>{{:: 'authz-policy-role-realm-roles.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" style="margin-top: -15px;">
<label class="col-md-2 control-label"></label>
- <div class="col-sm-3">
+ <div class="col-sm-4" data-ng-show="hasRealmRole()">
<table class="table table-striped table-bordered">
<thead>
- <tr data-ng-hide="!selectedRoles.length">
- <th>{{:: 'name' | translate}}</th>
+ <tr>
+ <th class="col-sm-5">{{:: 'name' | translate}}</th>
+ <th>{{:: 'authz-required' | translate}}</th>
+ <th>{{:: 'actions' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="role in selectedRoles | orderBy:'name'" 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>
</tr>
</thead>
<tbody>
- <tr ng-repeat="role in selectedRoles | orderBy:'name'">
+ <tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="role.clientRole">
<td>{{role.name}}</td>
+ <td>{{role.container.name}}</td>
+ <td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
<td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedRoles, $index);">{{:: 'remove' | translate}}</button>
+ <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
</td>
</tr>
<tr data-ng-show="!selectedRoles.length">
@@ -98,7 +152,6 @@
</div>
<input type="hidden" data-ng-model="policy.type"/>
</fieldset>
-
<div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
index 8a748f4..524d55d 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
@@ -43,14 +43,14 @@
<ul>
<li data-ng-repeat="policyResult in result.policies">
<strong><a
- href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
+ href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
decision was <span style="color: green" data-ng-show="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
<span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision.</a>
<ul>
<li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
<strong><a
- href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
+ href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
voted to <span style="color: green"
data-ng-show="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>
<span style="color: red" data-ng-hide="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>.</a>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
index 8d1b0e1..39cfc77 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
@@ -24,11 +24,33 @@
</tr>
<tr data-ng-hide="scopes.length == 0">
<th>{{:: 'name' | translate}}</th>
+ <th>{{:: 'authz-resources' | translate}}</th>
+ <th>{{:: 'authz-permissions' | translate}}</th>
+ <th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="scope in scopes | filter:search | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>
+ <td>
+ <span data-ng-show="!scope.resources.length">{{:: 'authz-no-resources-assigned' | translate}}</span>
+ <span data-ng-show="scope.resources.length > 0">
+ <span ng-repeat="resource in scope.resources">
+ <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource/{{resource._id}}">{{resource.name}}</a>{{$last ? '' : ', '}}
+ </span>
+ </span>
+ </td>
+ <td>
+ <span data-ng-show="!scope.policies.length">{{:: 'authz-no-permission-assigned' | translate}}</span>
+ <span data-ng-show="scope.policies.length > 0">
+ <span ng-repeat="policy in scope.policies">
+ <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a>{{$last ? '' : ', '}}
+ </span>
+ </span>
+ </td>
+ <td class="kc-action-cell" style="vertical-align: middle">
+ <button class="btn btn-default btn-block btn-sm" ng-click="createPolicy(scope);">{{:: 'authz-create-permission' | translate}}</button>
+ </td>
</tr>
<tr data-ng-show="(scopes | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>