Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
5facec73e4
122 changed files with 2951 additions and 763 deletions
|
@ -126,11 +126,13 @@ public abstract class AbstractPolicyEnforcer {
|
|||
List<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : permissions) {
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
|
||||
if (permission.getResourceSetId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
|
||||
}
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||
this.paths.remove(actualPathConfig);
|
||||
|
@ -138,11 +140,6 @@ public abstract class AbstractPolicyEnforcer {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((allowedScopes.isEmpty() && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +148,11 @@ public abstract class AbstractPolicyEnforcer {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
|
||||
}
|
||||
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return this.authzClient;
|
||||
}
|
||||
|
@ -210,7 +212,6 @@ public abstract class AbstractPolicyEnforcer {
|
|||
config.setPath(targetResource.getUri());
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setInstance(true);
|
||||
config.setParentConfig(originalConfig);
|
||||
|
||||
this.paths.add(config);
|
||||
|
@ -244,13 +245,17 @@ public abstract class AbstractPolicyEnforcer {
|
|||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
// first we try a match using resource id
|
||||
boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||
|
||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||
resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getParentConfig().getId());
|
||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||
}
|
||||
|
||||
return resourceMatch;
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||
int retry = 2;
|
||||
AccessToken original = accessToken;
|
||||
|
||||
while (retry >= 0) {
|
||||
while (retry > 0) {
|
||||
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
original.setAuthorization(accessToken.getAuthorization());
|
||||
return true;
|
||||
|
|
|
@ -45,7 +45,6 @@ public class PolicyEnforcer {
|
|||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
|
||||
private final KeycloakDeployment deployment;
|
||||
private final PathMatcher pathMatcher;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final List<PathConfig> paths;
|
||||
|
@ -54,7 +53,6 @@ public class PolicyEnforcer {
|
|||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
|
||||
this.pathMatcher = new PathMatcher();
|
||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
|
|
@ -71,6 +71,13 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
|
@ -121,12 +128,6 @@
|
|||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -23,9 +23,12 @@ import org.keycloak.authorization.model.Policy;
|
|||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles;
|
||||
|
||||
/**
|
||||
|
@ -47,22 +50,40 @@ public class RolePolicyProvider implements PolicyProvider {
|
|||
|
||||
@Override
|
||||
public void evaluate(Evaluation evaluation) {
|
||||
EvaluationContext context = evaluation.getContext();
|
||||
String[] roleIds = getRoles(this.policy);
|
||||
Map<String, Object>[] roleIds = getRoles(this.policy);
|
||||
|
||||
if (roleIds.length > 0) {
|
||||
Identity identity = context.getIdentity();
|
||||
Identity identity = evaluation.getContext().getIdentity();
|
||||
|
||||
for (String roleId : roleIds) {
|
||||
RoleModel role = getCurrentRealm().getRoleById(roleId);
|
||||
for (Map<String, Object> current : roleIds) {
|
||||
RoleModel role = getCurrentRealm().getRoleById((String) current.get("id"));
|
||||
|
||||
if (role != null && identity.hasRole(role.getName())) {
|
||||
if (role != null) {
|
||||
boolean hasRole = hasRole(identity, role);
|
||||
|
||||
if (!hasRole && Boolean.valueOf(isRequired(current))) {
|
||||
evaluation.deny();
|
||||
return;
|
||||
} else if (hasRole) {
|
||||
evaluation.grant();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRequired(Map<String, Object> current) {
|
||||
return (boolean) current.getOrDefault("required", false);
|
||||
}
|
||||
|
||||
private boolean hasRole(Identity identity, RoleModel role) {
|
||||
String roleName = role.getName();
|
||||
if (role.isClientRole()) {
|
||||
ClientModel clientModel = getCurrentRealm().getClientById(role.getContainerId());
|
||||
return identity.hasClientRole(clientModel.getClientId(), roleName);
|
||||
}
|
||||
return identity.hasRealmRole(roleName);
|
||||
}
|
||||
|
||||
private RealmModel getCurrentRealm() {
|
||||
return this.authorization.getKeycloakSession().getContext().getRealm();
|
||||
|
|
|
@ -34,7 +34,9 @@ import org.keycloak.util.JsonSerialization;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -81,11 +83,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
|
|||
RoleModel removedRole = ((RoleRemovedEvent) event).getRole();
|
||||
|
||||
policyStore.findByType(getId()).forEach(policy -> {
|
||||
List<String> roles = new ArrayList<>();
|
||||
List<Map> roles = new ArrayList<>();
|
||||
|
||||
for (String roleId : getRoles(policy)) {
|
||||
if (!roleId.equals(removedRole.getId())) {
|
||||
roles.add(roleId);
|
||||
for (Map<String,Object> role : getRoles(policy)) {
|
||||
if (!role.get("id").equals(removedRole.getId())) {
|
||||
Map updated = new HashMap();
|
||||
updated.put("id", role.get("id"));
|
||||
Object required = role.get("required");
|
||||
if (required != null) {
|
||||
updated.put("required", required);
|
||||
}
|
||||
roles.add(updated);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +104,9 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
|
|||
});
|
||||
policyStore.delete(policy.getId());
|
||||
} else {
|
||||
policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
|
||||
Map<String, String> config = policy.getConfig();
|
||||
config.put("roles", JsonSerialization.writeValueAsString(roles));
|
||||
policy.setConfig(config);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e);
|
||||
|
@ -116,17 +126,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
|
|||
return "role";
|
||||
}
|
||||
|
||||
static String[] getRoles(Policy policy) {
|
||||
static Map<String, Object>[] getRoles(Policy policy) {
|
||||
String roles = policy.getConfig().get("roles");
|
||||
|
||||
if (roles != null) {
|
||||
try {
|
||||
return JsonSerialization.readValue(roles.getBytes(), String[].class);
|
||||
return JsonSerialization.readValue(roles.getBytes(), Map[].class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new String[]{};
|
||||
return new Map[] {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,6 @@ public class PolicyEnforcerConfig {
|
|||
private List<MethodConfig> methods = new ArrayList<>();
|
||||
private List<String> scopes = Collections.emptyList();
|
||||
private String id;
|
||||
private boolean instance;
|
||||
|
||||
@JsonIgnore
|
||||
private PathConfig parentConfig;
|
||||
|
@ -178,11 +177,7 @@ public class PolicyEnforcerConfig {
|
|||
}
|
||||
|
||||
public boolean isInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void setInstance(boolean instance) {
|
||||
this.instance = instance;
|
||||
return this.parentConfig != null;
|
||||
}
|
||||
|
||||
public void setParentConfig(PathConfig parentConfig) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.keycloak.representations.idm.authorization;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -35,6 +37,7 @@ public class PolicyRepresentation {
|
|||
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
|
||||
private Map<String, String> config = new HashMap();
|
||||
private List<PolicyRepresentation> dependentPolicies;
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private List<PolicyRepresentation> associatedPolicies = new ArrayList<>();
|
||||
|
||||
public String getId() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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\"]"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,7 +22,12 @@
|
|||
],
|
||||
"realmRoles": [
|
||||
"user", "uma_authorization"
|
||||
],
|
||||
"clientRoles": {
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "jdoe",
|
||||
|
@ -38,7 +43,12 @@
|
|||
],
|
||||
"realmRoles": [
|
||||
"user", "uma_authorization"
|
||||
],
|
||||
"clientRoles": {
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "admin",
|
||||
|
@ -53,11 +63,14 @@
|
|||
}
|
||||
],
|
||||
"realmRoles": [
|
||||
"user", "admin", "uma_authorization"
|
||||
"admin", "uma_authorization"
|
||||
],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"realm-admin"
|
||||
],
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -90,6 +103,8 @@
|
|||
"adminUrl": "/photoz-html5-client",
|
||||
"baseUrl": "/photoz-html5-client",
|
||||
"publicClient": true,
|
||||
"consentRequired" : true,
|
||||
"fullScopeAllowed" : true,
|
||||
"redirectUris": [
|
||||
"/photoz-html5-client/*"
|
||||
],
|
||||
|
|
|
@ -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,6 +44,8 @@
|
|||
"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",
|
||||
|
@ -58,16 +60,20 @@
|
|||
"name": "Any Admin Policy",
|
||||
"description": "Defines that adminsitrators can do something",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"roles": "[\"admin\"]"
|
||||
"roles": "[{\"id\":\"admin\",\"required\":true}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Any User Policy",
|
||||
"description": "Defines that any user can do something",
|
||||
"description": "Defines that only users from well known clients are allowed to access",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"roles": "[\"user\"]"
|
||||
"roles": "[{\"id\":\"user\"},{\"id\":\"manage-albums\",\"required\":true}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -84,14 +90,17 @@
|
|||
"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\"]"
|
||||
"applyPolicies": "[\"Only From a Specific Client Address\",\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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\"]"
|
||||
|
@ -101,26 +110,17 @@
|
|||
"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",
|
||||
|
|
|
@ -112,7 +112,6 @@ public class AlbumService {
|
|||
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");
|
||||
|
|
|
@ -9,17 +9,18 @@
|
|||
"secret": "secret"
|
||||
},
|
||||
"policy-enforcer": {
|
||||
"user-managed-access" : {},
|
||||
"paths": [
|
||||
{
|
||||
"path" : "/album/*",
|
||||
"methods" : [
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:create"]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -30,6 +31,10 @@
|
|||
{
|
||||
"method": "DELETE",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:delete"]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
"description": "Defines that adminsitrators can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[\"admin\"]"
|
||||
"roles": "[{\"id\":\"admin\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -62,7 +62,7 @@
|
|||
"description": "Defines that any user can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[\"user\"]"
|
||||
"roles": "[{\"id\":\"user\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -71,7 +71,7 @@
|
|||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"config": {
|
||||
"roles": "[\"user_premium\"]"
|
||||
"roles": "[{\"id\":\"user_premium\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
String contextPath = request.getContextPath();
|
||||
String redirectUri = scheme + "://" + host + ":" + port + contextPath;
|
||||
%>
|
||||
<h2>Click <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8080/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">here</a> to logout.</h2>
|
||||
<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8080/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">Sign Out</a></h2>
|
|
@ -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) {
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -34,4 +34,5 @@
|
|||
<include file="META-INF/jpa-changelog-1.9.2.xml"/>
|
||||
|
||||
<include file="META-INF/jpa-changelog-authz-master.xml"/>
|
||||
<include file="META-INF/jpa-changelog-2.1.0.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -179,7 +179,7 @@
|
|||
<addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_ATTR_PK" tableName="FED_USER_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_CONSENT_PK" tableName="FED_USER_CONSENT"/>
|
||||
<addPrimaryKey columnNames="USER_CONSENT_ID, ROLE_ID" constraintName="CONSTR_USER_CONSENT_ROLE_PK" tableName="FED_USER_CONSENT_ROLE"/>
|
||||
<addPrimaryKey columnNames="USER_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTR_USER_CONSENT_PROT_MAP_PK" tableName="FED_USER_CONSENT_PROT_MAPPER"/>
|
||||
<addPrimaryKey columnNames="USER_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTR_USER_CONSENT_PROTM_PK" tableName="FED_USER_CONSENT_PROT_MAPPER"/>
|
||||
<!--
|
||||
<addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="FED_USER_CONSENT_ROLE" constraintName="FK_FED_GRNTCSNT_ROLE_GR" referencedColumnNames="ID" referencedTableName="FED_USER_CONSENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="FED_USER_CONSENT_PROT_MAPPER" constraintName="FK_FED_GRNTCSNT_PRM_GR" referencedColumnNames="ID" referencedTableName="FED_USER_CONSENT"/>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<include file="META-INF/jpa-changelog-1.9.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.9.1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.9.2.xml"/>
|
||||
<include file="META-INF/jpa-changelog-2.1.0.xml"/>
|
||||
|
||||
<include file="META-INF/jpa-changelog-authz-master.xml"/>
|
||||
<include file="META-INF/jpa-changelog-2.1.0.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -67,7 +67,8 @@ public interface Attributes {
|
|||
* @return true if any attribute with <code>name</code> and <code>value</code> exist. Otherwise, returns false.
|
||||
*/
|
||||
default boolean containsValue(String name, String value) {
|
||||
return toMap().getOrDefault(name, emptyList()).stream().anyMatch(value::equals);
|
||||
Collection<String> values = toMap().get(name);
|
||||
return values != null && values.stream().anyMatch(value::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,7 +96,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()]);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,10 @@ import org.keycloak.authorization.store.StoreFactory;
|
|||
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -130,27 +132,49 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (policy.getScopes().isEmpty()) {
|
||||
return true;
|
||||
Set<Scope> scopes = new HashSet<>(policy.getScopes());
|
||||
|
||||
if (scopes.isEmpty()) {
|
||||
Set<Resource> resources = new HashSet<>();
|
||||
|
||||
resources.addAll(policy.getResources());
|
||||
|
||||
for (Resource resource : resources) {
|
||||
scopes.addAll(resource.getScopes());
|
||||
}
|
||||
|
||||
boolean hasScope = true;
|
||||
if (!resources.isEmpty() && scopes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Scope givenScope : policy.getScopes()) {
|
||||
boolean hasGivenScope = false;
|
||||
if (scopes.isEmpty()) {
|
||||
Resource resource = permission.getResource();
|
||||
String type = resource.getType();
|
||||
|
||||
if (type != null) {
|
||||
List<Resource> resourcesByType = authorization.getStoreFactory().getResourceStore().findByType(type);
|
||||
|
||||
for (Resource resourceType : resourcesByType) {
|
||||
if (resourceType.getOwner().equals(resource.getResourceServer().getClientId())) {
|
||||
resources.add(resourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Resource resource : resources) {
|
||||
scopes.addAll(resource.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
for (Scope givenScope : scopes) {
|
||||
for (Scope scope : permission.getScopes()) {
|
||||
if (givenScope.getId().equals(scope.getId())) {
|
||||
hasGivenScope = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasGivenScope) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return hasScope;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,4 +71,8 @@ public interface Errors {
|
|||
String INVALID_EMAIL = "invalid_email";
|
||||
String IDENTITY_PROVIDER_LOGIN_FAILURE = "identity_provider_login_failure";
|
||||
String IDENTITY_PROVIDER_ERROR = "identity_provider_error";
|
||||
|
||||
String PASSWORD_CONFIRM_ERROR = "password_confirm_error";
|
||||
String PASSWORD_MISSING = "password_missing";
|
||||
String PASSWORD_REJECTED = "password_rejected";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,11 @@ public interface RealmModel extends RoleContainerModel {
|
|||
RealmModel getCreatedRealm();
|
||||
}
|
||||
|
||||
interface RealmPostCreateEvent extends ProviderEvent {
|
||||
RealmModel getCreatedRealm();
|
||||
KeycloakSession getKeycloakSession();
|
||||
}
|
||||
|
||||
interface RealmRemovedEvent extends ProviderEvent {
|
||||
RealmModel getRealm();
|
||||
KeycloakSession getKeycloakSession();
|
||||
|
|
|
@ -222,6 +222,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ import org.keycloak.Config;
|
|||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -84,17 +86,23 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
|||
String passwordNew = formData.getFirst("password-new");
|
||||
String passwordConfirm = formData.getFirst("password-confirm");
|
||||
|
||||
EventBuilder errorEvent = event.clone().event(EventType.UPDATE_PASSWORD_ERROR)
|
||||
.client(context.getClientSession().getClient())
|
||||
.user(context.getClientSession().getUserSession().getUser());
|
||||
|
||||
if (Validation.isBlank(passwordNew)) {
|
||||
Response challenge = context.form()
|
||||
.setError(Messages.MISSING_PASSWORD)
|
||||
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
context.challenge(challenge);
|
||||
errorEvent.error(Errors.PASSWORD_MISSING);
|
||||
return;
|
||||
} else if (!passwordNew.equals(passwordConfirm)) {
|
||||
Response challenge = context.form()
|
||||
.setError(Messages.NOTMATCH_PASSWORD)
|
||||
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
context.challenge(challenge);
|
||||
errorEvent.error(Errors.PASSWORD_CONFIRM_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -102,12 +110,14 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
|||
context.getSession().users().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew));
|
||||
context.success();
|
||||
} catch (ModelException me) {
|
||||
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
Response challenge = context.form()
|
||||
.setError(me.getMessage(), me.getParameters())
|
||||
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
context.challenge(challenge);
|
||||
return;
|
||||
} catch (Exception ape) {
|
||||
errorEvent.detail(Details.REASON, ape.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
Response challenge = context.form()
|
||||
.setError(ape.getMessage())
|
||||
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
|
|
|
@ -87,16 +87,17 @@ public class PolicyEvaluationService {
|
|||
@Consumes("application/json")
|
||||
@Produces("application/json")
|
||||
public void evaluate(PolicyEvaluationRequest evaluationRequest, @Suspended AsyncResponse asyncResponse) {
|
||||
EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest);
|
||||
authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(createDecisionCollector(evaluationRequest, authorization, asyncResponse));
|
||||
KeycloakIdentity identity = createIdentity(evaluationRequest);
|
||||
EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity);
|
||||
authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(createDecisionCollector(evaluationRequest, authorization, identity, asyncResponse));
|
||||
}
|
||||
|
||||
private DecisionResultCollector createDecisionCollector(PolicyEvaluationRequest evaluationRequest, AuthorizationProvider authorization, AsyncResponse asyncResponse) {
|
||||
private DecisionResultCollector createDecisionCollector(PolicyEvaluationRequest evaluationRequest, AuthorizationProvider authorization, KeycloakIdentity identity, AsyncResponse asyncResponse) {
|
||||
return new DecisionResultCollector() {
|
||||
@Override
|
||||
protected void onComplete(List<Result> results) {
|
||||
try {
|
||||
asyncResponse.resume(Response.ok(PolicyEvaluationResponse.build(evaluationRequest, results, resourceServer, authorization)).build());
|
||||
asyncResponse.resume(Response.ok(PolicyEvaluationResponse.build(evaluationRequest, results, resourceServer, authorization, identity)).build());
|
||||
} catch (Throwable cause) {
|
||||
asyncResponse.resume(cause);
|
||||
}
|
||||
|
@ -109,8 +110,8 @@ public class PolicyEvaluationService {
|
|||
};
|
||||
}
|
||||
|
||||
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation) {
|
||||
return new KeycloakEvaluationContext(createIdentity(representation), this.authorization.getKeycloakSession()) {
|
||||
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
|
||||
return new KeycloakEvaluationContext(identity, this.authorization.getKeycloakSession()) {
|
||||
@Override
|
||||
public Attributes getAttributes() {
|
||||
Map<String, Collection<String>> attributes = new HashMap<>(super.getAttributes().toMap());
|
||||
|
@ -136,11 +137,8 @@ public class PolicyEvaluationService {
|
|||
}
|
||||
|
||||
private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) {
|
||||
if (representation.isEntitlements()) {
|
||||
return Permissions.all(this.resourceServer, evaluationContext.getIdentity(), authorization);
|
||||
}
|
||||
|
||||
return representation.getResources().stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
|
||||
List<PolicyEvaluationRequest.Resource> resources = representation.getResources();
|
||||
return resources.stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
|
||||
Set<String> givenScopes = resource.getScopes();
|
||||
|
||||
if (givenScopes == null) {
|
||||
|
@ -157,7 +155,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());
|
||||
}
|
||||
|
@ -226,10 +230,16 @@ public class PolicyEvaluationService {
|
|||
}
|
||||
}
|
||||
|
||||
accessToken.addAccess(clientModel.getClientId());
|
||||
AccessToken.Access resourceAccess = accessToken.getResourceAccess(clientModel.getClientId());
|
||||
AccessToken.Access clientAccess = accessToken.addAccess(clientModel.getClientId());
|
||||
clientAccess.roles(new HashSet<>());
|
||||
|
||||
userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> resourceAccess.addRole(roleName));
|
||||
userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> clientAccess.addRole(roleName));
|
||||
|
||||
ClientModel resourceServerClient = realm.getClientById(resourceServer.getClientId());
|
||||
AccessToken.Access resourceServerAccess = accessToken.addAccess(resourceServerClient.getClientId());
|
||||
resourceServerAccess.roles(new HashSet<>());
|
||||
|
||||
userModel.getClientRoleMappings(resourceServerClient).stream().map(RoleModel::getName).forEach(roleName -> resourceServerAccess.addRole(roleName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,7 +154,11 @@ public class PolicyService {
|
|||
}
|
||||
|
||||
policyStore.findDependentPolicies(id).forEach(dependentPolicy -> {
|
||||
if (dependentPolicy.getAssociatedPolicies().size() == 1) {
|
||||
policyStore.delete(dependentPolicy.getId());
|
||||
} else {
|
||||
dependentPolicy.removeAssociatedPolicy(policy);
|
||||
}
|
||||
});
|
||||
|
||||
policyStore.delete(policy.getId());
|
||||
|
@ -271,7 +275,6 @@ public class PolicyService {
|
|||
}
|
||||
|
||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
|
||||
for (String scopeId : scopeIds) {
|
||||
boolean hasScope = false;
|
||||
|
|
|
@ -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;
|
||||
|
@ -47,6 +45,7 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
|||
import org.keycloak.services.resources.admin.RealmAuth;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.management.relation.Role;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
|
@ -63,6 +62,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
@ -146,7 +147,11 @@ public class ResourceServerService {
|
|||
.stream().map(resource -> {
|
||||
ResourceRepresentation rep = Models.toRepresentation(resource, resourceServer, authorization);
|
||||
|
||||
if (rep.getOwner().getId().equals(this.resourceServer.getClientId())) {
|
||||
rep.setOwner(null);
|
||||
} else {
|
||||
rep.getOwner().setId(null);
|
||||
}
|
||||
rep.setId(null);
|
||||
rep.setPolicies(null);
|
||||
rep.getScopes().forEach(scopeRepresentation -> {
|
||||
|
@ -175,15 +180,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 +256,102 @@ public class ResourceServerService {
|
|||
String roles = config.get("roles");
|
||||
|
||||
if (roles != null && !roles.isEmpty()) {
|
||||
roles = roles.replace("[", "");
|
||||
roles = roles.replace("]", "");
|
||||
try {
|
||||
List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
|
||||
config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> {
|
||||
String roleName = roleConfig.get("id").toString();
|
||||
String clientId = null;
|
||||
int clientIdSeparator = roleName.indexOf("/");
|
||||
|
||||
if (!roles.isEmpty()) {
|
||||
String roleNames = "";
|
||||
|
||||
for (String role : roles.split(",")) {
|
||||
if (!roleNames.isEmpty()) {
|
||||
roleNames = roleNames + ",";
|
||||
if (clientIdSeparator != -1) {
|
||||
clientId = roleName.substring(0, clientIdSeparator);
|
||||
roleName = roleName.substring(clientIdSeparator + 1);
|
||||
}
|
||||
|
||||
role = role.replace("\"", "");
|
||||
RoleModel role;
|
||||
|
||||
roleNames = roleNames + "\"" + this.realm.getRole(role).getId() + "\"";
|
||||
if (clientId == null) {
|
||||
role = realm.getRole(roleName);
|
||||
} else {
|
||||
role = realm.getClientByClientId(clientId).getRole(roleName);
|
||||
}
|
||||
|
||||
config.put("roles", "[" + roleNames + "]");
|
||||
// fallback to find any client role with the given name
|
||||
if (role == null) {
|
||||
String finalRoleName = roleName;
|
||||
role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
if (role == null) {
|
||||
throw new RuntimeException("Error while importing configuration. Role [" + role + "] could not be found.");
|
||||
}
|
||||
|
||||
roleConfig.put("id", role.getId());
|
||||
return roleConfig;
|
||||
}).collect(Collectors.toList())));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
String users = config.get("users");
|
||||
|
||||
if (users != null) {
|
||||
users = users.replace("[", "");
|
||||
users = users.replace("]", "");
|
||||
|
||||
if (!users.isEmpty()) {
|
||||
String userNames = "";
|
||||
|
||||
for (String user : users.split(",")) {
|
||||
if (!userNames.isEmpty()) {
|
||||
userNames = userNames + ",";
|
||||
}
|
||||
|
||||
user = user.replace("\"", "");
|
||||
|
||||
userNames = userNames + "\"" + this.session.users().getUserByUsername(user, this.realm).getId() + "\"";
|
||||
}
|
||||
|
||||
config.put("users", "[" + userNames + "]");
|
||||
if (users != null && !users.isEmpty()) {
|
||||
try {
|
||||
List<String> usersMap = JsonSerialization.readValue(users, List.class);
|
||||
config.put("users", JsonSerialization.writeValueAsString(usersMap.stream().map(userName -> this.session.users().getUserByUsername(userName, this.realm).getId()).collect(Collectors.toList())));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
String scopes = config.get("scopes");
|
||||
|
||||
if (scopes != null && !scopes.isEmpty()) {
|
||||
scopes = scopes.replace("[", "");
|
||||
scopes = scopes.replace("]", "");
|
||||
|
||||
if (!scopes.isEmpty()) {
|
||||
String scopeNames = "";
|
||||
|
||||
for (String scope : scopes.split(",")) {
|
||||
if (!scopeNames.isEmpty()) {
|
||||
scopeNames = scopeNames + ",";
|
||||
}
|
||||
|
||||
scope = scope.replace("\"", "");
|
||||
|
||||
Scope newScope = scopeStore.findByName(scope, resourceServer.getId());
|
||||
try {
|
||||
List<String> scopesMap = JsonSerialization.readValue(scopes, List.class);
|
||||
config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> {
|
||||
Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId());
|
||||
|
||||
if (newScope == null) {
|
||||
throw new RuntimeException("Scope with name [" + scope + "] not defined.");
|
||||
throw new RuntimeException("Scope with name [" + scopeName + "] not defined.");
|
||||
}
|
||||
|
||||
scopeNames = scopeNames + "\"" + newScope.getId() + "\"";
|
||||
}
|
||||
|
||||
config.put("scopes", "[" + scopeNames + "]");
|
||||
return newScope.getId();
|
||||
}).collect(Collectors.toList())));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
String policyResources = config.get("resources");
|
||||
|
||||
if (policyResources != null && !policyResources.isEmpty()) {
|
||||
policyResources = policyResources.replace("[", "");
|
||||
policyResources = policyResources.replace("]", "");
|
||||
|
||||
if (!policyResources.isEmpty()) {
|
||||
String resourceNames = "";
|
||||
|
||||
for (String resource : policyResources.split(",")) {
|
||||
if (!resourceNames.isEmpty()) {
|
||||
resourceNames = resourceNames + ",";
|
||||
}
|
||||
|
||||
resource = resource.replace("\"", "");
|
||||
|
||||
if ("".equals(resource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resourceNames = resourceNames + "\"" + storeFactory.getResourceStore().findByName(resource, resourceServer.getId()).getId() + "\"";
|
||||
}
|
||||
|
||||
config.put("resources", "[" + resourceNames + "]");
|
||||
try {
|
||||
List<String> resources = JsonSerialization.readValue(policyResources, List.class);
|
||||
config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> storeFactory.getResourceStore().findByName(resourceName, resourceServer.getId()).getId()).collect(Collectors.toList())));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
String applyPolicies = config.get("applyPolicies");
|
||||
|
||||
if (applyPolicies != null && !applyPolicies.isEmpty()) {
|
||||
applyPolicies = applyPolicies.replace("[", "");
|
||||
applyPolicies = applyPolicies.replace("]", "");
|
||||
|
||||
if (!applyPolicies.isEmpty()) {
|
||||
String policyNames = "";
|
||||
|
||||
for (String pId : applyPolicies.split(",")) {
|
||||
if (!policyNames.isEmpty()) {
|
||||
policyNames = policyNames + ",";
|
||||
}
|
||||
|
||||
pId = pId.replace("\"", "").trim();
|
||||
|
||||
Policy policy = policyStore.findByName(pId, resourceServer.getId());
|
||||
try {
|
||||
List<String> policies = JsonSerialization.readValue(applyPolicies, List.class);
|
||||
config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> {
|
||||
Policy policy = policyStore.findByName(policyName, resourceServer.getId());
|
||||
|
||||
if (policy == null) {
|
||||
throw new RuntimeException("Policy with name [" + pId + "] not defined.");
|
||||
throw new RuntimeException("Policy with name [" + policyName + "] not defined.");
|
||||
}
|
||||
|
||||
policyNames = policyNames + "\"" + policy.getId() + "\"";
|
||||
}
|
||||
|
||||
config.put("applyPolicies", "[" + policyNames + "]");
|
||||
return policy.getId();
|
||||
}).collect(Collectors.toList())));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -491,6 +460,7 @@ public class ResourceServerService {
|
|||
}
|
||||
|
||||
private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) {
|
||||
try {
|
||||
PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
|
||||
|
||||
rep.setId(null);
|
||||
|
@ -501,113 +471,48 @@ public class ResourceServerService {
|
|||
String roles = config.get("roles");
|
||||
|
||||
if (roles != null && !roles.isEmpty()) {
|
||||
roles = roles.replace("[", "");
|
||||
roles = roles.replace("]", "");
|
||||
|
||||
if (!roles.isEmpty()) {
|
||||
String roleNames = "";
|
||||
|
||||
for (String role : roles.split(",")) {
|
||||
if (!roleNames.isEmpty()) {
|
||||
roleNames = roleNames + ",";
|
||||
}
|
||||
|
||||
role = role.replace("\"", "");
|
||||
|
||||
roleNames = roleNames + "\"" + this.realm.getRoleById(role).getName() + "\"";
|
||||
}
|
||||
|
||||
config.put("roles", "[" + roleNames + "]");
|
||||
}
|
||||
List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
|
||||
config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
|
||||
roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
|
||||
return roleMap;
|
||||
}).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
String users = config.get("users");
|
||||
|
||||
if (users != null) {
|
||||
users = users.replace("[", "");
|
||||
users = users.replace("]", "");
|
||||
|
||||
if (!users.isEmpty()) {
|
||||
if (users != null && !users.isEmpty()) {
|
||||
UserFederationManager userManager = this.session.users();
|
||||
String userNames = "";
|
||||
|
||||
for (String user : users.split(",")) {
|
||||
if (!userNames.isEmpty()) {
|
||||
userNames = userNames + ",";
|
||||
}
|
||||
|
||||
user = user.replace("\"", "");
|
||||
|
||||
userNames = userNames + "\"" + userManager.getUserById(user, this.realm).getUsername() + "\"";
|
||||
}
|
||||
|
||||
config.put("users", "[" + userNames + "]");
|
||||
}
|
||||
List<String> userIds = JsonSerialization.readValue(users, List.class);
|
||||
config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, this.realm).getUsername()).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
String scopes = config.get("scopes");
|
||||
|
||||
if (scopes != null && !scopes.isEmpty()) {
|
||||
scopes = scopes.replace("[", "");
|
||||
scopes = scopes.replace("]", "");
|
||||
|
||||
if (!scopes.isEmpty()) {
|
||||
ScopeStore scopeStore = storeFactory.getScopeStore();
|
||||
String scopeNames = "";
|
||||
|
||||
for (String scope : scopes.split(",")) {
|
||||
if (!scopeNames.isEmpty()) {
|
||||
scopeNames = scopeNames + ",";
|
||||
}
|
||||
|
||||
scope = scope.replace("\"", "");
|
||||
|
||||
scopeNames = scopeNames + "\"" + scopeStore.findById(scope).getName() + "\"";
|
||||
}
|
||||
|
||||
config.put("scopes", "[" + scopeNames + "]");
|
||||
}
|
||||
List<String> scopeIds = JsonSerialization.readValue(scopes, List.class);
|
||||
config.put("scopes", JsonSerialization.writeValueAsString(scopeIds.stream().map(scopeId -> scopeStore.findById(scopeId).getName()).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
String policyResources = config.get("resources");
|
||||
|
||||
if (policyResources != null && !policyResources.isEmpty()) {
|
||||
policyResources = policyResources.replace("[", "");
|
||||
policyResources = policyResources.replace("]", "");
|
||||
|
||||
if (!policyResources.isEmpty()) {
|
||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||
String resourceNames = "";
|
||||
|
||||
for (String resource : policyResources.split(",")) {
|
||||
if (!resourceNames.isEmpty()) {
|
||||
resourceNames = resourceNames + ",";
|
||||
List<String> resourceIds = JsonSerialization.readValue(policyResources, List.class);
|
||||
config.put("resources", JsonSerialization.writeValueAsString(resourceIds.stream().map(resourceId -> resourceStore.findById(resourceId).getName()).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
resource = resource.replace("\"", "");
|
||||
|
||||
resourceNames = resourceNames + "\"" + resourceStore.findById(resource).getName() + "\"";
|
||||
}
|
||||
|
||||
config.put("resources", "[" + resourceNames + "]");
|
||||
}
|
||||
}
|
||||
|
||||
String policyNames = "";
|
||||
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
|
||||
|
||||
if (!associatedPolicies.isEmpty()) {
|
||||
for (Policy associatedPolicy : associatedPolicies) {
|
||||
if (!policyNames.isEmpty()) {
|
||||
policyNames = policyNames + ",";
|
||||
config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
policyNames = policyNames + "\"" + associatedPolicy.getName() + "\"";
|
||||
}
|
||||
|
||||
config.put("applyPolicies", "[" + policyNames + "]");
|
||||
}
|
||||
rep.setAssociatedPolicies(null);
|
||||
|
||||
return rep;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,13 +21,14 @@ 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.common.KeycloakIdentity;
|
||||
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.protocol.oidc.TokenManager;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
@ -46,14 +47,22 @@ public class PolicyEvaluationResponse {
|
|||
private List<EvaluationResultRepresentation> results;
|
||||
private boolean entitlements;
|
||||
private Effect status;
|
||||
private AccessToken rpt;
|
||||
|
||||
private PolicyEvaluationResponse() {
|
||||
|
||||
}
|
||||
|
||||
public static PolicyEvaluationResponse build(PolicyEvaluationRequest evaluationRequest, List<Result> results, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
||||
public static PolicyEvaluationResponse build(PolicyEvaluationRequest evaluationRequest, List<Result> results, ResourceServer resourceServer, AuthorizationProvider authorization, KeycloakIdentity identity) {
|
||||
PolicyEvaluationResponse response = new PolicyEvaluationResponse();
|
||||
List<EvaluationResultRepresentation> resultsRep = new ArrayList<>();
|
||||
AccessToken accessToken = identity.getAccessToken();
|
||||
AccessToken.Authorization authorizationData = new AccessToken.Authorization();
|
||||
|
||||
authorizationData.setPermissions(Permissions.allPermits(results));
|
||||
accessToken.setAuthorization(authorizationData);
|
||||
|
||||
response.rpt = accessToken;
|
||||
|
||||
if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Effect.DENY))) {
|
||||
response.status = Effect.DENY;
|
||||
|
@ -90,9 +99,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;
|
||||
|
@ -120,6 +137,10 @@ public class PolicyEvaluationResponse {
|
|||
return entitlements;
|
||||
}
|
||||
|
||||
public AccessToken getRpt() {
|
||||
return rpt;
|
||||
}
|
||||
|
||||
public static class EvaluationResultRepresentation {
|
||||
|
||||
private ResourceRepresentation resource;
|
||||
|
|
|
@ -63,13 +63,18 @@ public final class Models {
|
|||
scope.setId(model.getId());
|
||||
scope.setName(model.getName());
|
||||
scope.setIconUri(model.getIconUri());
|
||||
|
||||
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
|
||||
|
||||
scope.setResources(new ArrayList<>());
|
||||
|
||||
storeFactory.getResourceStore().findByScope(model.getId()).forEach(resource -> scope.getResources().add(toRepresentation(resource, resource.getResourceServer(), authorizationProvider)));
|
||||
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
|
||||
scope.setPolicies(new ArrayList<>());
|
||||
|
||||
Set<Policy> policies = new HashSet<>();
|
||||
|
||||
policies.addAll(authorizationProvider.getStoreFactory().getPolicyStore().findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()));
|
||||
|
||||
for (Policy policyModel : policies) {
|
||||
policyStore.findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()).forEach(policyModel -> {
|
||||
PolicyRepresentation policy = new PolicyRepresentation();
|
||||
|
||||
policy.setId(policyModel.getId());
|
||||
|
@ -79,7 +84,7 @@ public final class Models {
|
|||
if (!scope.getPolicies().contains(policy)) {
|
||||
scope.getPolicies().add(policy);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.authorization.authorization;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.authorization.representation.AuthorizationRequest;
|
||||
|
@ -50,8 +51,6 @@ import javax.ws.rs.container.Suspended;
|
|||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -104,8 +103,12 @@ public class AuthorizationTokenService {
|
|||
List<Permission> entitlements = Permissions.allPermits(results);
|
||||
|
||||
if (entitlements.isEmpty()) {
|
||||
HashMap<Object, Object> error = new HashMap<>();
|
||||
|
||||
error.put(OAuth2Constants.ERROR, "not_authorized");
|
||||
|
||||
asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
|
||||
.entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
|
||||
.entity(error))
|
||||
.allowedOrigins(identity.getAccessToken())
|
||||
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
|
||||
} else {
|
||||
|
@ -193,14 +196,7 @@ public class AuthorizationTokenService {
|
|||
return permissionsToEvaluate.entrySet().stream()
|
||||
.flatMap((Function<Entry<String, Set<String>>, Stream<ResourcePermission>>) entry -> {
|
||||
Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey());
|
||||
if (entry.getValue().isEmpty()) {
|
||||
return Arrays.asList(new ResourcePermission(entryResource, Collections.emptyList(), entryResource.getResourceServer())).stream();
|
||||
} else {
|
||||
return entry.getValue().stream()
|
||||
.map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, entryResource.getResourceServer().getId()))
|
||||
.filter(scope -> scope != null)
|
||||
.map(scope -> new ResourcePermission(entryResource, Arrays.asList(scope), entryResource.getResourceServer()));
|
||||
}
|
||||
return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream();
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.saml.common.util.StringUtil;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
|
@ -37,6 +38,7 @@ import java.util.Collection;
|
|||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -53,61 +55,59 @@ public class KeycloakIdentity implements Identity {
|
|||
}
|
||||
|
||||
public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
|
||||
this.accessToken = accessToken;
|
||||
|
||||
if (this.accessToken == null) {
|
||||
if (accessToken == null) {
|
||||
throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (keycloakSession == null) {
|
||||
throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN);
|
||||
}
|
||||
this.accessToken = accessToken;
|
||||
this.keycloakSession = keycloakSession;
|
||||
this.realm = keycloakSession.getContext().getRealm();
|
||||
|
||||
HashMap<String, Collection<String>> attributes = new HashMap<>();
|
||||
Map<String, Collection<String>> attributes = new HashMap<>();
|
||||
|
||||
try {
|
||||
ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
|
||||
Iterator<String> iterator = objectNode.fieldNames();
|
||||
List<String> roleNames = new ArrayList<>();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
String fieldName = iterator.next();
|
||||
JsonNode fieldValue = objectNode.get(fieldName);
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
values.add(fieldValue.asText());
|
||||
if (fieldValue.isArray()) {
|
||||
Iterator<JsonNode> valueIterator = fieldValue.iterator();
|
||||
|
||||
if (fieldName.equals("realm_access")) {
|
||||
JsonNode grantedRoles = fieldValue.get("roles");
|
||||
|
||||
if (grantedRoles != null) {
|
||||
Iterator<JsonNode> rolesIt = grantedRoles.iterator();
|
||||
|
||||
while (rolesIt.hasNext()) {
|
||||
roleNames.add(rolesIt.next().asText());
|
||||
}
|
||||
while (valueIterator.hasNext()) {
|
||||
values.add(valueIterator.next().asText());
|
||||
}
|
||||
} else {
|
||||
String value = fieldValue.asText();
|
||||
|
||||
if (StringUtil.isNullOrEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldName.equals("resource_access")) {
|
||||
Iterator<JsonNode> resourceAccessIt = fieldValue.iterator();
|
||||
|
||||
while (resourceAccessIt.hasNext()) {
|
||||
JsonNode grantedRoles = resourceAccessIt.next().get("roles");
|
||||
|
||||
if (grantedRoles != null) {
|
||||
Iterator<JsonNode> rolesIt = grantedRoles.iterator();
|
||||
|
||||
while (rolesIt.hasNext()) {
|
||||
roleNames.add(rolesIt.next().asText());
|
||||
}
|
||||
}
|
||||
}
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
if (!values.isEmpty()) {
|
||||
attributes.put(fieldName, values);
|
||||
}
|
||||
}
|
||||
|
||||
attributes.put("roles", roleNames);
|
||||
AccessToken.Access realmAccess = accessToken.getRealmAccess();
|
||||
|
||||
if (realmAccess != null) {
|
||||
attributes.put("kc.realm.roles", realmAccess.getRoles());
|
||||
}
|
||||
|
||||
Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
|
||||
|
||||
if (resourceAccess != null) {
|
||||
resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while reading attributes from security token.", e);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.authorization.entitlement;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.common.KeycloakEvaluationContext;
|
||||
|
@ -55,8 +56,6 @@ import javax.ws.rs.container.Suspended;
|
|||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -119,8 +118,12 @@ public class EntitlementService {
|
|||
List<Permission> entitlements = Permissions.allPermits(results);
|
||||
|
||||
if (entitlements.isEmpty()) {
|
||||
HashMap<Object, Object> error = new HashMap<>();
|
||||
|
||||
error.put(OAuth2Constants.ERROR, "not_authorized");
|
||||
|
||||
asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
|
||||
.entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
|
||||
.entity(error))
|
||||
.allowedOrigins(identity.getAccessToken())
|
||||
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
|
||||
} else {
|
||||
|
@ -249,15 +252,7 @@ public class EntitlementService {
|
|||
return permissionsToEvaluate.entrySet().stream()
|
||||
.flatMap((Function<Map.Entry<String, Set<String>>, Stream<ResourcePermission>>) entry -> {
|
||||
Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey());
|
||||
|
||||
if (entry.getValue().isEmpty()) {
|
||||
return Arrays.asList(new ResourcePermission(entryResource, Collections.emptyList(), entryResource.getResourceServer())).stream();
|
||||
} else {
|
||||
return entry.getValue().stream()
|
||||
.map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, entryResource.getResourceServer().getId()))
|
||||
.filter(scope -> scope != null)
|
||||
.map(scope -> new ResourcePermission(entryResource, Arrays.asList(scope), entryResource.getResourceServer()));
|
||||
}
|
||||
return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream();
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.authorization.model.Scope;
|
|||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
import org.keycloak.authorization.store.ScopeStore;
|
||||
import org.keycloak.authorization.store.StoreFactory;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
|
@ -61,73 +62,95 @@ 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, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization)));
|
||||
resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization)));
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public static List<ResourcePermission> createResourcePermissions(Resource resource) {
|
||||
public static List<ResourcePermission> createResourcePermissions(Resource resource, Set<String> requestedScopes, AuthorizationProvider authorization) {
|
||||
List<ResourcePermission> permissions = new ArrayList<>();
|
||||
List<Scope> scopes = resource.getScopes();
|
||||
String type = resource.getType();
|
||||
ResourceServer resourceServer = resource.getResourceServer();
|
||||
List<Scope> scopes;
|
||||
|
||||
if (requestedScopes.isEmpty()) {
|
||||
scopes = resource.getScopes();
|
||||
// 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 && !resource.getOwner().equals(resourceServer.getClientId())) {
|
||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||
resourceStore.findByType(type).forEach(resource1 -> {
|
||||
if (resource1.getOwner().equals(resourceServer.getClientId())) {
|
||||
for (Scope typeScope : resource1.getScopes()) {
|
||||
if (!scopes.contains(typeScope)) {
|
||||
scopes.add(typeScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
|
||||
scopes = requestedScopes.stream().map(scopeName -> {
|
||||
Scope byName = scopeStore.findByName(scopeName, resource.getResourceServer().getId());
|
||||
return byName;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (scopes.isEmpty()) {
|
||||
permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
|
||||
|
||||
} else {
|
||||
for (Scope scope : scopes) {
|
||||
permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
|
||||
}
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public static List<Permission> allPermits(List<Result> evaluation) {
|
||||
List<Permission> permissions = evaluation.stream()
|
||||
.filter(evaluationResult -> evaluationResult.getEffect().equals(Effect.PERMIT))
|
||||
.map(evaluationResult -> {
|
||||
ResourcePermission permission = evaluationResult.getPermission();
|
||||
String resourceId = null;
|
||||
String resourceName = null;
|
||||
Map<String, Permission> permissions = new HashMap<>();
|
||||
|
||||
for (Result evaluationResult : evaluation) {
|
||||
ResourcePermission permission = evaluationResult.getPermission();
|
||||
Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
|
||||
if (evaluationResult.getEffect().equals(Effect.DENY)) {
|
||||
continue;
|
||||
}
|
||||
Resource resource = permission.getResource();
|
||||
|
||||
if (resource != null) {
|
||||
resourceId = resource.getId();
|
||||
resourceName = resource.getName();
|
||||
}
|
||||
|
||||
Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
|
||||
|
||||
return new Permission(resourceId, resourceName, scopes);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
Map<String, Permission> perms = new HashMap<>();
|
||||
|
||||
permissions.forEach(permission -> {
|
||||
Permission evalPermission = perms.get(permission.getResourceSetId());
|
||||
String resourceId = resource.getId();
|
||||
String resourceName = resource.getName();
|
||||
Permission evalPermission = permissions.get(resource.getId());
|
||||
|
||||
if (evalPermission == null) {
|
||||
evalPermission = permission;
|
||||
perms.put(permission.getResourceSetId(), evalPermission);
|
||||
evalPermission = new Permission(resourceId, resourceName, scopes);
|
||||
permissions.put(resourceId, evalPermission);
|
||||
}
|
||||
|
||||
Set<String> permissionScopes = permission.getScopes();
|
||||
if (scopes != null && !scopes.isEmpty()) {
|
||||
Set<String> finalScopes = evalPermission.getScopes();
|
||||
|
||||
if (permissionScopes != null && !permissionScopes.isEmpty()) {
|
||||
Set<String> scopes = evalPermission.getScopes();
|
||||
|
||||
if (scopes == null) {
|
||||
scopes = new HashSet();
|
||||
evalPermission.setScopes(scopes);
|
||||
if (finalScopes == null) {
|
||||
finalScopes = new HashSet();
|
||||
evalPermission.setScopes(finalScopes);
|
||||
}
|
||||
|
||||
for (String scopeName : permissionScopes) {
|
||||
if (!scopes.contains(scopeName)) {
|
||||
scopes.add(scopeName);
|
||||
for (String scopeName : scopes) {
|
||||
if (!finalScopes.contains(scopeName)) {
|
||||
finalScopes.add(scopeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Permission scopePermission = new Permission(null, null, scopes);
|
||||
permissions.put(scopePermission.toString(), scopePermission);
|
||||
}
|
||||
}
|
||||
|
||||
return perms.values().stream().collect(Collectors.toList());
|
||||
return permissions.values().stream().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,8 @@ public class RealmManager implements RealmImporter {
|
|||
setupOfflineTokens(realm);
|
||||
setupAuthorizationServices(realm);
|
||||
|
||||
fireRealmPostCreate(realm);
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
|
@ -491,6 +493,7 @@ public class RealmManager implements RealmImporter {
|
|||
}
|
||||
|
||||
setupAuthorizationServices(realm);
|
||||
fireRealmPostCreate(realm);
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
@ -587,4 +590,19 @@ public class RealmManager implements RealmImporter {
|
|||
private void setupAuthorizationServices(RealmModel realm) {
|
||||
KeycloakModelUtils.setupAuthorizationServices(realm);
|
||||
}
|
||||
|
||||
private void fireRealmPostCreate(RealmModel realm) {
|
||||
session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() {
|
||||
@Override
|
||||
public RealmModel getCreatedRealm() {
|
||||
return realm;
|
||||
}
|
||||
@Override
|
||||
public KeycloakSession getKeycloakSession() {
|
||||
return session;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.forms.account.AccountPages;
|
||||
import org.keycloak.forms.account.AccountProvider;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -612,26 +612,34 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
String passwordNew = formData.getFirst("password-new");
|
||||
String passwordConfirm = formData.getFirst("password-confirm");
|
||||
|
||||
EventBuilder errorEvent = event.clone().event(EventType.UPDATE_PASSWORD_ERROR)
|
||||
.client(auth.getClient())
|
||||
.user(auth.getClientSession().getUserSession().getUser());
|
||||
|
||||
if (requireCurrent) {
|
||||
if (Validation.isBlank(password)) {
|
||||
setReferrerOnPage();
|
||||
errorEvent.error(Errors.PASSWORD_MISSING);
|
||||
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
||||
UserCredentialModel cred = UserCredentialModel.password(password);
|
||||
if (!session.users().validCredentials(session, realm, user, cred)) {
|
||||
setReferrerOnPage();
|
||||
errorEvent.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
if (Validation.isBlank(passwordNew)) {
|
||||
setReferrerOnPage();
|
||||
errorEvent.error(Errors.PASSWORD_MISSING);
|
||||
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
||||
if (!passwordNew.equals(passwordConfirm)) {
|
||||
setReferrerOnPage();
|
||||
errorEvent.error(Errors.PASSWORD_CONFIRM_ERROR);
|
||||
return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
||||
|
@ -639,14 +647,17 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
||||
} catch (ModelReadOnlyException mre) {
|
||||
setReferrerOnPage();
|
||||
errorEvent.error(Errors.NOT_ALLOWED);
|
||||
return account.setError(Messages.READ_ONLY_PASSWORD).createResponse(AccountPages.PASSWORD);
|
||||
} catch (ModelException me) {
|
||||
logger.failedToUpdatePassword(me);
|
||||
setReferrerOnPage();
|
||||
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
return account.setError(me.getMessage(), me.getParameters()).createResponse(AccountPages.PASSWORD);
|
||||
} catch (Exception ape) {
|
||||
logger.failedToUpdatePassword(ape);
|
||||
setReferrerOnPage();
|
||||
errorEvent.detail(Details.REASON, ape.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ public class ClientAttributeCertificateResource {
|
|||
*
|
||||
* @param uriInfo
|
||||
* @param input
|
||||
* @return
|
||||
* @return information extracted from uploaded certificate - not necessarily the new state of certificate on the server
|
||||
* @throws IOException
|
||||
*/
|
||||
@POST
|
||||
|
@ -189,6 +189,7 @@ public class ClientAttributeCertificateResource {
|
|||
|
||||
if (info.getCertificate() != null) {
|
||||
client.setAttribute(certificateAttribute, info.getCertificate());
|
||||
client.removeAttribute(privateAttribute);
|
||||
} else {
|
||||
throw new ErrorResponseException("certificate-not-found", "Certificate with given alias not found in the keystore", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
|
|
@ -69,6 +69,11 @@
|
|||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.shrinkwrap.resolver</groupId>
|
||||
<artifactId>shrinkwrap-resolver-impl-maven</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.arquillian.extension</groupId>
|
||||
<artifactId>arquillian-drone-bom</artifactId>
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,7 +22,12 @@
|
|||
],
|
||||
"realmRoles": [
|
||||
"user", "uma_authorization"
|
||||
],
|
||||
"clientRoles": {
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "jdoe",
|
||||
|
@ -38,7 +43,12 @@
|
|||
],
|
||||
"realmRoles": [
|
||||
"user", "uma_authorization"
|
||||
],
|
||||
"clientRoles": {
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "admin",
|
||||
|
@ -53,11 +63,14 @@
|
|||
}
|
||||
],
|
||||
"realmRoles": [
|
||||
"user", "admin", "uma_authorization"
|
||||
"admin", "uma_authorization"
|
||||
],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"realm-admin"
|
||||
],
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -90,6 +103,8 @@
|
|||
"adminUrl": "/photoz-html5-client",
|
||||
"baseUrl": "/photoz-html5-client",
|
||||
"publicClient": true,
|
||||
"consentRequired" : true,
|
||||
"fullScopeAllowed" : true,
|
||||
"redirectUris": [
|
||||
"/photoz-html5-client/*"
|
||||
],
|
||||
|
|
|
@ -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",
|
||||
"description": "Defines that only users from well known clients are allowed to access",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"roles": "[\"user\"]"
|
||||
"applyPolicies": "[]",
|
||||
"roles": "[{\"id\":\"user\"},{\"id\":\"manage-albums\",\"required\":true}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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,14 +94,17 @@
|
|||
"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\"]"
|
||||
"applyPolicies": "[\"Only From a Specific Client Address\",\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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\"]"
|
||||
|
@ -101,26 +114,18 @@
|
|||
"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",
|
||||
|
|
|
@ -112,7 +112,6 @@ public class AlbumService {
|
|||
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");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"realm": "photoz",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "http://localhost:8080/auth",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"ssl-required": "external",
|
||||
"resource": "photoz-restful-api",
|
||||
"bearer-only" : true,
|
||||
|
@ -9,17 +9,18 @@
|
|||
"secret": "secret"
|
||||
},
|
||||
"policy-enforcer": {
|
||||
"user-managed-access" : {},
|
||||
"paths": [
|
||||
{
|
||||
"path" : "/album/*",
|
||||
"methods" : [
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:create"]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -30,6 +31,10 @@
|
|||
{
|
||||
"method": "DELETE",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:delete"]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -20,5 +20,6 @@
|
|||
<module>js-database</module>
|
||||
<module>photoz</module>
|
||||
<module>hello-world-authz-service</module>
|
||||
<module>servlet-authz</module>
|
||||
</modules>
|
||||
</project>
|
|
@ -0,0 +1,54 @@
|
|||
# About the Example Application
|
||||
|
||||
This is a simple Servlet-based application that will introduce you to some of the main concepts around Keycloak Authorization Services.
|
||||
|
||||
For this application, users can be regular users, premium users or administrators, where:
|
||||
|
||||
* Regular users have very limited access.
|
||||
* Premium users have access to the *premium area*
|
||||
* Administrators have access to the *administration area*
|
||||
|
||||
In Keycloak, all the paths being protected are resources on the server.
|
||||
|
||||
This application will also show you how to create a dynamic menu with the permissions granted to an user.
|
||||
|
||||
## Create the Example Realm and a Resource Server
|
||||
|
||||
Considering that your Keycloak Server is up and running, log in to the Keycloak Administration Console.
|
||||
|
||||
Now, create a new realm based on the following configuration file:
|
||||
|
||||
examples/authz/servlet-authz/servlet-authz-realm.json
|
||||
|
||||
That will import a pre-configured realm with everything you need to run this example. For more details about how to import a realm
|
||||
into Keycloak, check the Keycloak's reference documentation.
|
||||
|
||||
After importing that file, you'll have a new realm called ``servlet-authz``.
|
||||
|
||||
Now, let's import another configuration using the Administration Console in order to configure the client application ``servlet-authz-app`` as a resource server with all resources, scopes, permissions and policies.
|
||||
|
||||
Click on ``Clients`` on the left side menu. Click on the ``servlet-authz-app`` on the client listing page. This will
|
||||
open the ``Client Details`` page. Once there, click on the `Authorization` tab.
|
||||
|
||||
Click on the ``Select file`` button, which means you want to import a resource server configuration. Now select the file that is located at:
|
||||
|
||||
examples/authz/servlet-authz/servlet-authz-app-config.json
|
||||
|
||||
Now click ``Upload`` and the resource server will be updated accordingly.
|
||||
|
||||
## Deploy and Run the Example Applications
|
||||
|
||||
To deploy the example application, follow these steps:
|
||||
|
||||
cd examples/authz/servlet-authz
|
||||
mvn clean package wildfly:deploy
|
||||
|
||||
Now, try to access the client application using the following URL:
|
||||
|
||||
http://localhost:8080/servlet-authz-app
|
||||
|
||||
If everything is correct, you will be redirect to Keycloak login page. You can login to the application with the following credentials:
|
||||
|
||||
* username: jdoe / password: jdoe
|
||||
* username: alice / password: alice
|
||||
* username: admin / password: admin
|
53
testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
Executable file
53
testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
Executable file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-test-apps</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>servlet-authz-app</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>Keycloak Authz: Servlet Authorization Test</name>
|
||||
<description>Servlet Authorization Test</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
"allowRemoteResourceManagement": true,
|
||||
"policyEnforcementMode": "ENFORCING",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Admin Resource",
|
||||
"uri": "/protected/admin/*",
|
||||
"type": "http://servlet-authz/protected/admin",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:servlet-authz:protected:admin:access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Protected Resource",
|
||||
"uri": "/*",
|
||||
"type": "http://servlet-authz/protected/resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:servlet-authz:protected:resource:access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Premium Resource",
|
||||
"uri": "/protected/premium/*",
|
||||
"type": "urn:servlet-authz:protected:resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:servlet-authz:protected:premium:access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Main Page",
|
||||
"type": "urn:servlet-authz:protected:resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:servlet-authz:page:main:actionForAdmin"
|
||||
},
|
||||
{
|
||||
"name": "urn:servlet-authz:page:main:actionForUser"
|
||||
},
|
||||
{
|
||||
"name": "urn:servlet-authz:page:main:actionForPremiumUser"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"name": "Any Admin Policy",
|
||||
"description": "Defines that adminsitrators can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"admin\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Any User Policy",
|
||||
"description": "Defines that any user can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"user\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only Premium User Policy",
|
||||
"description": "Defines that only premium users can do something",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"user_premium\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "All Users Policy",
|
||||
"description": "Defines that all users can do something",
|
||||
"type": "aggregate",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Premium Resource Permission",
|
||||
"description": "A policy that defines access to premium resources",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Premium Resource\"]",
|
||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Administrative Resource Permission",
|
||||
"description": "A policy that defines access to administrative resources",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Admin Resource\"]",
|
||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Protected Resource Permission",
|
||||
"description": "A policy that defines access to any protected resource",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"resources": "[\"Protected Resource\"]",
|
||||
"applyPolicies": "[\"All Users Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 1 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 1 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]",
|
||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 2 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 2 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]",
|
||||
"applyPolicies": "[\"Any User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 3 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 3 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]",
|
||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"realm": "servlet-authz",
|
||||
"enabled": true,
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [
|
||||
"password"
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"username": "alice",
|
||||
"enabled": true,
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": "alice"
|
||||
}
|
||||
],
|
||||
"realmRoles": [
|
||||
"user"
|
||||
]
|
||||
},
|
||||
{
|
||||
"username": "jdoe",
|
||||
"enabled": true,
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": "jdoe"
|
||||
}
|
||||
],
|
||||
"realmRoles": [
|
||||
"user",
|
||||
"user_premium"
|
||||
]
|
||||
},
|
||||
{
|
||||
"username": "admin",
|
||||
"enabled": true,
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": "admin"
|
||||
}
|
||||
],
|
||||
"realmRoles": [
|
||||
"user",
|
||||
"admin"
|
||||
],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"realm-admin"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "service-account-servlet-authz-app",
|
||||
"enabled": true,
|
||||
"serviceAccountClientId": "servlet-authz-app",
|
||||
"clientRoles": {
|
||||
"servlet-authz-app" : ["uma_protection"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"roles": {
|
||||
"realm": [
|
||||
{
|
||||
"name": "user",
|
||||
"description": "User privileges"
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Administrator privileges"
|
||||
},
|
||||
{
|
||||
"name": "user_premium",
|
||||
"description": "User Premium privileges"
|
||||
}
|
||||
]
|
||||
},
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "servlet-authz-app",
|
||||
"enabled": true,
|
||||
"baseUrl": "/servlet-authz-app",
|
||||
"adminUrl": "/servlet-authz-app",
|
||||
"bearerOnly": false,
|
||||
"authorizationServicesEnabled": true,
|
||||
"redirectUris": [
|
||||
"/servlet-authz-app/*"
|
||||
],
|
||||
"secret": "secret"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
~
|
||||
-->
|
||||
|
||||
<jboss-deployment-structure>
|
||||
<deployment>
|
||||
<dependencies>
|
||||
<module name="org.keycloak.keycloak-authz-client" services="import"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"realm": "servlet-authz",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url" : "http://localhost:8180/auth",
|
||||
"ssl-required" : "external",
|
||||
"resource" : "servlet-authz-app",
|
||||
"public-client" : false,
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"policy-enforcer": {
|
||||
"on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>servlet-authz-app</module-name>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>All Resources</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>user</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>All Resources</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>admin</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>servlet-authz</realm-name>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
|
||||
<error-page>
|
||||
<error-code>403</error-code>
|
||||
<location>/accessDenied.jsp</location>
|
||||
</error-page>
|
||||
|
||||
</web-app>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2 style="color: red">You can not access this resource.</h2>
|
||||
<%@include file="logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
<%@page import="org.keycloak.AuthorizationContext" %>
|
||||
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
|
||||
<%@ page import="org.keycloak.representations.idm.authorization.Permission" %>
|
||||
|
||||
<%
|
||||
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
|
||||
%>
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<%@include file="logout-include.jsp"%>
|
||||
<h2>This is a public resource. Try to access one of these <i>protected</i> resources:</h2>
|
||||
|
||||
<p><a href="protected/dynamicMenu.jsp">Dynamic Menu</a></p>
|
||||
<p><a href="protected/premium/onlyPremium.jsp">User Premium</a></p>
|
||||
<p><a href="protected/admin/onlyAdmin.jsp">Administration</a></p>
|
||||
|
||||
<h3>Your permissions are:</h3>
|
||||
|
||||
<ul>
|
||||
<%
|
||||
for (Permission permission : authzContext.getPermissions()) {
|
||||
%>
|
||||
<li>
|
||||
<p>Resource: <%= permission.getResourceSetName() %></p>
|
||||
<p>ID: <%= permission.getResourceSetId() %></p>
|
||||
<p>Scopes: <%= permission.getScopes() %></p>
|
||||
</li>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
|
||||
<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
|
||||
<%
|
||||
String scheme = request.getScheme();
|
||||
String host = request.getServerName();
|
||||
int port = request.getServerPort();
|
||||
String contextPath = request.getContextPath();
|
||||
String redirectUri = scheme + "://" + host + ":" + port + contextPath;
|
||||
%>
|
||||
<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8180/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">Sign Out</a></h2>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>Only Administrators can access this page.</h2>
|
||||
<%@include file="../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,48 @@
|
|||
<%@page import="org.keycloak.AuthorizationContext" %>
|
||||
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
|
||||
|
||||
<%
|
||||
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
|
||||
%>
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<h2>Any authenticated user can access this page.</h2>
|
||||
<%@include file="../logout-include.jsp"%>
|
||||
|
||||
<p>Here is a dynamic menu built from the permissions returned by the server:</p>
|
||||
|
||||
<ul>
|
||||
<%
|
||||
if (authzContext.hasResourcePermission("Protected Resource")) {
|
||||
%>
|
||||
<li>
|
||||
Do user thing
|
||||
</li>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
|
||||
<%
|
||||
if (authzContext.hasResourcePermission("Premium Resource")) {
|
||||
%>
|
||||
<li>
|
||||
Do user premium thing
|
||||
</li>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
|
||||
<%
|
||||
if (authzContext.hasPermission("Admin Resource", "urn:servlet-authz:protected:admin:access")) {
|
||||
%>
|
||||
<li>
|
||||
Do administration thing
|
||||
</li>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>Only for premium users.</h2>
|
||||
<%@include file="../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -43,5 +43,13 @@
|
|||
<exclude name="**/subsystem-config.xml"/>
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="target/test-apps/servlet-authz-app" overwrite="true">
|
||||
<fileset dir="../servlet-authz">
|
||||
<exclude name="**/target/**"/>
|
||||
<exclude name="**/*.iml"/>
|
||||
<exclude name="**/*.unconfigured"/>
|
||||
<exclude name="**/subsystem-config.xml"/>
|
||||
</fileset>
|
||||
</copy>
|
||||
</target>
|
||||
</project>
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class BadClientSalesPostSigServlet extends SAMLServletWithLogout {
|
||||
public class BadClientSalesPostSigServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "bad-client-sales-post-sig";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class BadRealmSalesPostSigServlet extends SAMLServletWithLogout {
|
||||
public class BadRealmSalesPostSigServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "bad-realm-sales-post-sig";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class Employee2Servlet extends SAMLServletWithLogout {
|
||||
public class Employee2Servlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "employee2";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class EmployeeSigFrontServlet extends SAMLServletWithLogout {
|
||||
public class EmployeeSigFrontServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "employee-sig-front";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class EmployeeSigServlet extends SAMLServletWithLogout {
|
||||
public class EmployeeSigServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "employee-sig";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -22,11 +22,13 @@ import org.jboss.arquillian.test.api.ArquillianResource;
|
|||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.pages.ConsentPage;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
|
@ -44,8 +46,14 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
@Page
|
||||
protected OIDCLogin loginPage;
|
||||
|
||||
@Page
|
||||
protected ConsentPage consentPage;
|
||||
|
||||
public void createAlbum(String name) {
|
||||
this.driver.findElement(By.id("create-album")).click();
|
||||
navigateTo();
|
||||
By id = By.id("create-album");
|
||||
WaitUtils.waitUntilElement(id);
|
||||
this.driver.findElement(id).click();
|
||||
Form.setInputValue(this.driver.findElement(By.id("album.name")), name);
|
||||
this.driver.findElement(By.id("save-album")).click();
|
||||
pause(500);
|
||||
|
@ -76,17 +84,68 @@ 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);
|
||||
|
||||
// simple check if we are at the consent page, if so just click 'Yes'
|
||||
if (this.consentPage.isCurrent()) {
|
||||
consentPage.confirm();
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
public void loginWithScopes(String username, String password, String... scopes) throws Exception {
|
||||
navigateTo();
|
||||
Thread.sleep(2000);
|
||||
if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) {
|
||||
Thread.sleep(2000);
|
||||
logOut();
|
||||
navigateTo();
|
||||
}
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
StringBuilder scopesValue = new StringBuilder();
|
||||
|
||||
for (String scope : scopes) {
|
||||
if (scopesValue.length() != 0) {
|
||||
scopesValue.append(" ");
|
||||
}
|
||||
scopesValue.append(scope);
|
||||
}
|
||||
|
||||
this.driver.navigate().to(this.driver.getCurrentUrl() + " " + scopesValue);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
this.loginPage.form().login(username, password);
|
||||
|
||||
// simple check if we are at the consent page, if so just click 'Yes'
|
||||
if (this.consentPage.isCurrent()) {
|
||||
consentPage.confirm();
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasDenied() {
|
||||
return this.driver.findElement(By.id("output")).getText().contains("You can not access");
|
||||
}
|
||||
|
||||
public void viewAlbum(String name) throws InterruptedException {
|
||||
Thread.sleep(2000);
|
||||
By id = By.id("view-" + name);
|
||||
WaitUtils.waitUntilElement(id);
|
||||
this.driver.findElements(id).forEach(WebElement::click);
|
||||
pause(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,24 @@ import static org.keycloak.testsuite.util.WaitUtils.pause;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public abstract class SAMLServletWithLogout extends AbstractPageWithInjectedUrl {
|
||||
public abstract class SAMLServlet extends AbstractPageWithInjectedUrl {
|
||||
|
||||
public void logout() {
|
||||
driver.navigate().to(getUriBuilder().queryParam("GLO", "true").build().toASCIIString());
|
||||
getUriBuilder().replaceQueryParam("GLO", null);
|
||||
pause(300);
|
||||
}
|
||||
|
||||
public void checkRoles(boolean check) {
|
||||
if (check) {
|
||||
getUriBuilder().queryParam("checkRoles", true);
|
||||
} else {
|
||||
getUriBuilder().replaceQueryParam("checkRoles", null);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkRolesEndPoint() {
|
||||
driver.navigate().to(getUriBuilder().build().toASCIIString() + "/checkRoles");
|
||||
pause(300);
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesMetadataServlet extends SAMLServletWithLogout {
|
||||
public class SalesMetadataServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-metadata";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostEncServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostEncServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post-enc";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostPassiveServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostPassiveServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post-passive";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostSigEmailServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostSigEmailServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post-sig-email";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostSigPersistentServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostSigPersistentServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post-sig-persistent";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostSigServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostSigServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post-sig";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.net.URL;
|
|||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SalesPostSigTransientServlet extends SAMLServletWithLogout {
|
||||
public class SalesPostSigTransientServlet extends SAMLServlet {
|
||||
public static final String DEPLOYMENT_NAME = "sales-post-sig-transient";
|
||||
|
||||
@ArquillianResource
|
||||
|
|
|
@ -17,78 +17,91 @@
|
|||
|
||||
package org.keycloak.testsuite.adapter.servlet;
|
||||
|
||||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author mhajas
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SendUsernameServlet extends HttpServlet {
|
||||
@Path("/")
|
||||
public class SendUsernameServlet {
|
||||
|
||||
public static Principal sentPrincipal;
|
||||
public static List<String> checkRoles;
|
||||
private static boolean checkRoles = false;
|
||||
|
||||
@Override
|
||||
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
|
||||
System.out.println("In SendUsername Servlet doGet()");
|
||||
if (checkRoles != null) {
|
||||
for (String role : checkRoles) {
|
||||
System.out.println("check role: " + role);
|
||||
//Assert.assertTrue(req.isUserInRole(role));
|
||||
if (!req.isUserInRole(role)) {
|
||||
resp.sendError(403);
|
||||
return;
|
||||
}
|
||||
@Context
|
||||
private HttpServletRequest httpServletRequest;
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
public Response doGet(@QueryParam("checkRoles") boolean checkRolesFlag) throws ServletException, IOException {
|
||||
System.out.println("In SendUsername Servlet doGet() check roles is " + (checkRolesFlag || checkRoles));
|
||||
if (httpServletRequest.getUserPrincipal() != null && (checkRolesFlag || checkRoles) && !checkRoles()) {
|
||||
return Response.status(Response.Status.FORBIDDEN).entity("Forbidden").build();
|
||||
}
|
||||
|
||||
return Response.ok(getOutput(), MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
resp.setContentType("text/plain");
|
||||
OutputStream stream = resp.getOutputStream();
|
||||
Principal principal = req.getUserPrincipal();
|
||||
stream.write("request-path: ".getBytes());
|
||||
stream.write(req.getServletPath().getBytes());
|
||||
stream.write("\n".getBytes());
|
||||
stream.write("principal=".getBytes());
|
||||
|
||||
@POST
|
||||
@NoCache
|
||||
public Response doPost(@QueryParam("checkRoles") boolean checkRolesFlag) throws ServletException, IOException {
|
||||
System.out.println("In SendUsername Servlet doPost() check roles is " + (checkRolesFlag || checkRoles));
|
||||
|
||||
if (httpServletRequest.getUserPrincipal() != null && (checkRolesFlag || checkRoles) && !checkRoles()) {
|
||||
throw new RuntimeException("User: " + httpServletRequest.getUserPrincipal() + " do not have required role");
|
||||
}
|
||||
|
||||
return Response.ok(getOutput(), MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{path}")
|
||||
public Response doGetElseWhere(@PathParam("path") String path, @QueryParam("checkRoles") boolean checkRolesFlag) throws ServletException, IOException {
|
||||
System.out.println("In SendUsername Servlet doGetElseWhere() - path: " + path);
|
||||
return doGet(checkRolesFlag);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{path}")
|
||||
public Response doPostElseWhere(@PathParam("path") String path, @QueryParam("checkRoles") boolean checkRolesFlag) throws ServletException, IOException {
|
||||
System.out.println("In SendUsername Servlet doPostElseWhere() - path: " + path);
|
||||
return doPost(checkRolesFlag);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("checkRoles")
|
||||
public String checkRolesEndPoint() {
|
||||
checkRoles = true;
|
||||
System.out.println("Setting checkRoles to true");
|
||||
return "Roles will be checked";
|
||||
}
|
||||
|
||||
private boolean checkRoles() {
|
||||
return httpServletRequest.isUserInRole("manager");
|
||||
}
|
||||
|
||||
private String getOutput() {
|
||||
String output = "request-path: ";
|
||||
output += httpServletRequest.getServletPath();
|
||||
output += "\n";
|
||||
output += "principal=";
|
||||
Principal principal = httpServletRequest.getUserPrincipal();
|
||||
|
||||
if (principal == null) {
|
||||
stream.write("null".getBytes());
|
||||
return;
|
||||
return output + "null";
|
||||
}
|
||||
String name = principal.getName();
|
||||
stream.write(name.getBytes());
|
||||
sentPrincipal = principal;
|
||||
|
||||
}
|
||||
@Override
|
||||
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
|
||||
System.out.println("In SendUsername Servlet doPost()");
|
||||
if (checkRoles != null) {
|
||||
for (String role : checkRoles) {
|
||||
System.out.println("check role: " + role);
|
||||
if (!req.isUserInRole(role)) {
|
||||
throw new RuntimeException("User: " + req.getUserPrincipal() + " is not in Role: " + role);
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.setContentType("text/plain");
|
||||
OutputStream stream = resp.getOutputStream();
|
||||
Principal principal = req.getUserPrincipal();
|
||||
stream.write("request-path: ".getBytes());
|
||||
stream.write(req.getServletPath().getBytes());
|
||||
stream.write("\n".getBytes());
|
||||
stream.write("principal=".getBytes());
|
||||
if (principal == null) {
|
||||
stream.write("null".getBytes());
|
||||
return;
|
||||
}
|
||||
String name = principal.getName();
|
||||
stream.write(name.getBytes());
|
||||
sentPrincipal = principal;
|
||||
return output + principal.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,9 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.logging.Logger.Level;
|
||||
import org.jboss.shrinkwrap.api.Archive;
|
||||
import org.jboss.shrinkwrap.api.asset.StringAsset;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UseServletFilter;
|
||||
import org.keycloak.testsuite.util.IOUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.w3c.dom.Document;
|
||||
|
@ -35,11 +37,9 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.hasAppServerContainerAnnotation;
|
||||
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.isRelative;
|
||||
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.isTomcatAppServer;
|
||||
|
||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.*;
|
||||
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.*;
|
||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerContextRoot;
|
||||
import static org.keycloak.testsuite.util.IOUtil.*;
|
||||
|
||||
;
|
||||
|
@ -155,18 +155,47 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
|
|||
}
|
||||
|
||||
protected void modifyWebXml(Archive<?> archive, TestClass testClass) {
|
||||
if (isTomcatAppServer(testClass.getJavaClass())) {
|
||||
try {
|
||||
String webXmlContent = IOUtils.toString(
|
||||
archive.get(WEBXML_PATH).getAsset().openStream());
|
||||
|
||||
if (isTomcatAppServer(testClass.getJavaClass())) {
|
||||
webXmlContent = webXmlContent.replace("<auth-method>KEYCLOAK</auth-method>", "<auth-method>BASIC</auth-method>");
|
||||
}
|
||||
|
||||
if (testClass.getJavaClass().isAnnotationPresent(UseServletFilter.class)) {
|
||||
//We need to add filter declaration to web.xml
|
||||
log.info("Adding filter to " + testClass.getAnnotation(UseServletFilter.class).filterClass() + " with mapping " + testClass.getAnnotation(UseServletFilter.class).filterPattern() + " for " + archive.getName());
|
||||
String filter = "\n<filter>\n" +
|
||||
"<filter-name>" + testClass.getAnnotation(UseServletFilter.class).filterName() + "</filter-name>\n" +
|
||||
"<filter-class>" + testClass.getAnnotation(UseServletFilter.class).filterClass() + "</filter-class>\n" +
|
||||
"</filter>\n" +
|
||||
"\n<filter-mapping>\n" +
|
||||
"<filter-name>" + testClass.getAnnotation(UseServletFilter.class).filterName() + "</filter-name>\n" +
|
||||
"<url-pattern>" + testClass.getAnnotation(UseServletFilter.class).filterPattern() + "</url-pattern>\n";
|
||||
if (!testClass.getAnnotation(UseServletFilter.class).dispatcherType().isEmpty()) {
|
||||
filter += "<dispatcher>" + testClass.getAnnotation(UseServletFilter.class).dispatcherType() + "</dispatcher>\n";
|
||||
}
|
||||
filter += "</filter-mapping>\n";
|
||||
|
||||
webXmlContent = webXmlContent.replace("</module-name>", "</module-name> " + filter);
|
||||
|
||||
//Also we need to add all dependencies within war lib directory, because filter needs to work without installed adapter
|
||||
log.info("Adding SAMLFilter dependencies to " + archive.getName());
|
||||
((WebArchive) archive).addAsLibraries(new SAMLFilterDependency().getDependencies());
|
||||
|
||||
|
||||
//finally we need to remove all keycloak related configuration from web.xml
|
||||
int start = webXmlContent.indexOf("<security-constraint>");
|
||||
int end = webXmlContent.indexOf("</security-role>") + "</security-role>".length();
|
||||
|
||||
|
||||
webXmlContent = webXmlContent.substring(0, start) + webXmlContent.substring(end);
|
||||
}
|
||||
|
||||
archive.add(new StringAsset((webXmlContent)), WEBXML_PATH);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Cannot load web.xml from archive.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package org.keycloak.testsuite.arquillian;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
|
||||
import org.jboss.shrinkwrap.resolver.api.maven.PackagingType;
|
||||
import org.jboss.shrinkwrap.resolver.api.maven.ScopeType;
|
||||
import org.jboss.shrinkwrap.resolver.api.maven.coordinate.MavenDependency;
|
||||
import org.jboss.shrinkwrap.resolver.api.maven.coordinate.MavenDependencyExclusion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class SAMLFilterDependency implements MavenDependency {
|
||||
|
||||
private static File[] files;
|
||||
|
||||
protected final Logger log = org.jboss.logging.Logger.getLogger(this.getClass());
|
||||
|
||||
@Override
|
||||
public Set<MavenDependencyExclusion> getExclusions() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScopeType getScope() {
|
||||
return ScopeType.COMPILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptional() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackagingType getPackaging() {
|
||||
return PackagingType.JAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackagingType getType() {
|
||||
return PackagingType.JAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassifier() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return System.getProperty("project.version");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupId() {
|
||||
return "org.keycloak";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return "keycloak-saml-servlet-filter-adapter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toCanonicalForm() {
|
||||
return getGroupId() + ":" + getArtifactId() + ":" + getVersion();
|
||||
}
|
||||
|
||||
private void resolve() {
|
||||
log.info("Resolving SAMLFilter dependencies");
|
||||
files = Maven.configureResolver().addDependency(this)
|
||||
.resolve().withTransitivity().asFile();
|
||||
log.info("Resolving dependencies is finished with " + files.length + " files");
|
||||
}
|
||||
|
||||
public File[] getDependencies() {
|
||||
if (files == null) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package org.keycloak.testsuite.arquillian.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Inherited
|
||||
public @interface UseServletFilter {
|
||||
|
||||
String filterName();
|
||||
String filterClass();
|
||||
String filterPattern() default "/*";
|
||||
String dispatcherType() default "";
|
||||
}
|
|
@ -21,6 +21,7 @@ import org.junit.Before;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.services.resources.AccountService;
|
||||
|
@ -168,22 +169,20 @@ public class AccountTest extends TestRealmKeycloakTest {
|
|||
EventRepresentation event = events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
|
||||
String sessionId = event.getSessionId();
|
||||
String userId = event.getUserId();
|
||||
changePasswordPage.changePassword("", "new-password", "new-password");
|
||||
|
||||
changePasswordPage.changePassword("", "new-password", "new-password");
|
||||
Assert.assertEquals("Please specify password.", profilePage.getError());
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_MISSING).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password", "new-password", "new-password2");
|
||||
|
||||
Assert.assertEquals("Password confirmation doesn't match.", profilePage.getError());
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_CONFIRM_ERROR).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password", "new-password", "new-password");
|
||||
|
||||
Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
|
||||
|
||||
changePasswordPage.logout();
|
||||
|
||||
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, changePasswordPage.getPath()).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
@ -191,7 +190,7 @@ public class AccountTest extends TestRealmKeycloakTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().session((String) null).error("invalid_user_credentials")
|
||||
events.expectLogin().session((String) null).error(Errors.INVALID_USER_CREDENTIALS)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
|
||||
|
@ -214,18 +213,14 @@ public class AccountTest extends TestRealmKeycloakTest {
|
|||
|
||||
changePasswordPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("", "new", "new");
|
||||
|
||||
Assert.assertEquals("Please specify password.", profilePage.getError());
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_MISSING).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password", "new-password", "new-password");
|
||||
|
||||
Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
|
||||
}
|
||||
|
||||
|
@ -235,31 +230,26 @@ public class AccountTest extends TestRealmKeycloakTest {
|
|||
|
||||
changePasswordPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password", "password", "password");
|
||||
|
||||
Assert.assertEquals("Invalid password: must not be equal to any of last 2 passwords.", profilePage.getError());
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password", "password1", "password1");
|
||||
|
||||
Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password1", "password", "password");
|
||||
|
||||
Assert.assertEquals("Invalid password: must not be equal to any of last 2 passwords.", profilePage.getError());
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password1", "password1", "password1");
|
||||
|
||||
Assert.assertEquals("Invalid password: must not be equal to any of last 2 passwords.", profilePage.getError());
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("password1", "password2", "password2");
|
||||
|
||||
Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
|
||||
}
|
||||
|
||||
|
|
|
@ -59,31 +59,28 @@ public abstract class AbstractDefaultAuthzConfigAdapterTest extends AbstractExam
|
|||
|
||||
@Test
|
||||
public void testDefaultAuthzConfig() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
configureAuthorizationServices();
|
||||
deploy();
|
||||
navigateToResourceServer();
|
||||
|
||||
login();
|
||||
|
||||
assertTrue(this.driver.getPageSource().contains("Your permissions are"));
|
||||
assertTrue(this.driver.getPageSource().contains("Default Resource"));
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void login() {
|
||||
this.loginPage.form().login("alice", "alice");
|
||||
}
|
||||
|
||||
private void navigateToResourceServer() throws MalformedURLException {
|
||||
private void login() throws MalformedURLException {
|
||||
this.driver.navigate().to(getResourceServerUrl());
|
||||
this.loginPage.form().login("alice", "alice");
|
||||
}
|
||||
|
||||
private URL getResourceServerUrl() throws MalformedURLException {
|
||||
return this.appServerContextRootPage.getUriBuilder().path(RESOURCE_SERVER_ID).build().toURL();
|
||||
}
|
||||
|
||||
private void deploy() {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
|
||||
private void configureAuthorizationServices() {
|
||||
ClientsResource clients = realmsResouce().realm(REALM_NAME).clients();
|
||||
ClientRepresentation client = clients.findByClientId(RESOURCE_SERVER_ID).get(0);
|
||||
|
|
|
@ -16,30 +16,42 @@
|
|||
*/
|
||||
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;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.ResourcesResource;
|
||||
import org.keycloak.admin.client.resource.RoleResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
||||
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
|
||||
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
@ -88,7 +100,7 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDeleteAlbum() throws Exception {
|
||||
public void testUserCanCreateAndDeleteAlbum() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
|
@ -96,13 +108,11 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
this.clientPage.createAlbum("Alice Family Album");
|
||||
|
||||
List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
|
||||
|
||||
assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
|
||||
|
||||
this.clientPage.deleteAlbum("Alice Family Album");
|
||||
|
||||
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);
|
||||
|
@ -115,11 +125,11 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
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();
|
||||
|
||||
List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
|
||||
|
||||
assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
|
@ -130,11 +140,11 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
}
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.deleteAlbum("Alice-Family-Album");
|
||||
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
resources = getAuthorizationResource().resources().resources();
|
||||
|
||||
assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
|
@ -144,12 +154,10 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
}
|
||||
}
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
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);
|
||||
|
@ -160,9 +168,9 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
public void testRegularUserCanNotAccessAdminResources() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
|
@ -173,9 +181,9 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
public void testAdminOnlyFromSpecificAddress() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
|
@ -186,10 +194,395 @@ 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);
|
||||
}
|
||||
if ("Any User Policy".equals(policy.getName())) {
|
||||
ClientResource resourceServerClient = getClientResource(RESOURCE_SERVER_ID);
|
||||
RoleResource manageAlbumRole = resourceServerClient.roles().get("manage-albums");
|
||||
RoleRepresentation roleRepresentation = manageAlbumRole.toRepresentation();
|
||||
List<Map> roles = JsonSerialization.readValue(policy.getConfig().get("roles"), List.class);
|
||||
|
||||
roles = roles.stream().filter(new Predicate<Map>() {
|
||||
@Override
|
||||
public boolean test(Map map) {
|
||||
return !map.get("id").equals(roleRepresentation.getId());
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
|
||||
|
||||
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 testAdminWithoutPermissionsToDeleteAlbum() 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientRoleRepresentingUserConsent() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
|
||||
List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
|
||||
|
||||
assertFalse(users.isEmpty());
|
||||
|
||||
UserRepresentation userRepresentation = users.get(0);
|
||||
UserResource userResource = usersResource.get(userRepresentation.getId());
|
||||
|
||||
ClientResource html5ClientApp = getClientResource("photoz-html5-client");
|
||||
|
||||
userResource.revokeConsent(html5ClientApp.toRepresentation().getClientId());
|
||||
|
||||
ClientResource resourceServerClient = getClientResource(RESOURCE_SERVER_ID);
|
||||
RoleResource roleResource = resourceServerClient.roles().get("manage-albums");
|
||||
RoleRepresentation roleRepresentation = roleResource.toRepresentation();
|
||||
|
||||
roleRepresentation.setScopeParamRequired(true);
|
||||
|
||||
roleResource.update(roleRepresentation);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.loginWithScopes("alice", "alice", RESOURCE_SERVER_ID + "/manage-albums");
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientRoleNotRequired() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
|
||||
List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
|
||||
|
||||
assertFalse(users.isEmpty());
|
||||
|
||||
UserRepresentation userRepresentation = users.get(0);
|
||||
UserResource userResource = usersResource.get(userRepresentation.getId());
|
||||
|
||||
ClientResource html5ClientApp = getClientResource("photoz-html5-client");
|
||||
|
||||
userResource.revokeConsent(html5ClientApp.toRepresentation().getClientId());
|
||||
|
||||
ClientResource resourceServerClient = getClientResource(RESOURCE_SERVER_ID);
|
||||
RoleResource manageAlbumRole = resourceServerClient.roles().get("manage-albums");
|
||||
RoleRepresentation roleRepresentation = manageAlbumRole.toRepresentation();
|
||||
|
||||
roleRepresentation.setScopeParamRequired(true);
|
||||
|
||||
manageAlbumRole.update(roleRepresentation);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
if ("Any User Policy".equals(policy.getName())) {
|
||||
List<Map> roles = JsonSerialization.readValue(policy.getConfig().get("roles"), List.class);
|
||||
|
||||
roles.forEach(role -> {
|
||||
String roleId = (String) role.get("id");
|
||||
if (roleId.equals(manageAlbumRole.toRepresentation().getId())) {
|
||||
role.put("required", false);
|
||||
}
|
||||
});
|
||||
|
||||
policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
|
||||
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
|
||||
}
|
||||
}
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverridePermissionFromResourceParent() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
String resourceName = "My Resource Instance";
|
||||
this.clientPage.createAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateTo();
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.createAlbum(resourceName);
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();;
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
this.clientPage.createAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
getAuthorizationResource().resources().resources().forEach(resource -> {
|
||||
if (resource.getName().equals(resourceName)) {
|
||||
try {
|
||||
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
|
||||
|
||||
resourceInstancePermission.setName(resourceName + "Permission");
|
||||
resourceInstancePermission.setType("resource");
|
||||
|
||||
Map<String, String> config = new HashMap<>();
|
||||
|
||||
config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
|
||||
config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only Owner Policy")));
|
||||
|
||||
resourceInstancePermission.setConfig(config);
|
||||
getAuthorizationResource().policies().create(resourceInstancePermission);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error creating policy.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
ResourcesResource resourcesResource = getAuthorizationResource().resources();
|
||||
List<ResourceRepresentation> resources = resourcesResource.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 testInheritPermissionFromResourceParent() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
|
||||
String resourceName = "My Resource Instance";
|
||||
this.clientPage.createAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateTo();
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.createAlbum(resourceName);
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();;
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
this.clientPage.createAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
ResourcesResource resourcesResource = getAuthorizationResource().resources();
|
||||
resourcesResource.resources().forEach(resource -> {
|
||||
if (resource.getName().equals(resourceName)) {
|
||||
try {
|
||||
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
|
||||
|
||||
resourceInstancePermission.setName(resourceName + "Permission");
|
||||
resourceInstancePermission.setType("resource");
|
||||
|
||||
Map<String, String> config = new HashMap<>();
|
||||
|
||||
config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
|
||||
config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only Owner Policy")));
|
||||
|
||||
resourceInstancePermission.setConfig(config);
|
||||
getAuthorizationResource().policies().create(resourceInstancePermission);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error creating policy.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
resourcesResource.resources().forEach(resource -> {
|
||||
if (resource.getName().equals(resourceName)) {
|
||||
resource.setScopes(resource.getScopes().stream().filter(scope -> !scope.getName().equals("urn:photoz.com:scopes:album:view")).collect(Collectors.toSet()));
|
||||
resourcesResource.resource(resource.getId()).update(resource);
|
||||
}
|
||||
});
|
||||
|
||||
this.clientPage.login("admin", "admin");
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.viewAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.navigateToAdminAlbum();
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertTrue(this.clientPage.wasDenied());
|
||||
|
||||
this.clientPage.login("alice", "alice");
|
||||
this.clientPage.deleteAlbum(resourceName);
|
||||
assertFalse(this.clientPage.wasDenied());
|
||||
List<ResourceRepresentation> resources = resourcesResource.resources();
|
||||
assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
|
||||
|
||||
resourcesResource.resources().forEach(resource -> {
|
||||
if (resource.getName().equals(resourceName)) {
|
||||
resource.setScopes(Collections.emptySet());
|
||||
resourcesResource.resource(resource.getId()).update(resource);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
|
@ -200,8 +593,12 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
}
|
||||
|
||||
private AuthorizationResource getAuthorizationResource() throws FileNotFoundException {
|
||||
return getClientResource(RESOURCE_SERVER_ID).authorization();
|
||||
}
|
||||
|
||||
private ClientResource getClientResource(String clientId) {
|
||||
ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
|
||||
ClientRepresentation resourceServer = clients.findByClientId(RESOURCE_SERVER_ID).get(0);
|
||||
return clients.get(resourceServer.getId()).authorization();
|
||||
ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
|
||||
return clients.get(resourceServer.getId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.adapter.example.authorization;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployer;
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.AuthorizationResource;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
||||
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.IOUtil.loadJson;
|
||||
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAdapterTest {
|
||||
|
||||
private static final String REALM_NAME = "servlet-authz";
|
||||
private static final String RESOURCE_SERVER_ID = "servlet-authz-app";
|
||||
|
||||
@ArquillianResource
|
||||
private Deployer deployer;
|
||||
|
||||
@Override
|
||||
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
|
||||
testRealms.add(
|
||||
loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json")));
|
||||
}
|
||||
|
||||
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
|
||||
public static WebArchive deployment() throws IOException {
|
||||
return exampleDeployment(RESOURCE_SERVER_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeAbstractKeycloakTest() throws Exception {
|
||||
super.beforeAbstractKeycloakTest();
|
||||
importResourceServerSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegularUserPermissions() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
assertTrue(hasLink("User Premium"));
|
||||
assertTrue(hasLink("Administration"));
|
||||
assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
|
||||
assertFalse(hasText("urn:servlet-authz:page:main:actionForAdmin"));
|
||||
assertFalse(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
|
||||
|
||||
navigateToDynamicMenuPage();
|
||||
assertTrue(hasText("Do user thing"));
|
||||
assertFalse(hasText("Do user premium thing"));
|
||||
assertFalse(hasText("Do administration thing"));
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertTrue(wasDenied());
|
||||
|
||||
navigateToAdminPage();
|
||||
assertTrue(wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserPremiumPermissions() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
login("jdoe", "jdoe");
|
||||
assertFalse(wasDenied());
|
||||
assertTrue(hasLink("User Premium"));
|
||||
assertTrue(hasLink("Administration"));
|
||||
assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
|
||||
assertTrue(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
|
||||
assertFalse(hasText("urn:servlet-authz:page:main:actionForAdmin"));
|
||||
|
||||
navigateToDynamicMenuPage();
|
||||
assertTrue(hasText("Do user thing"));
|
||||
assertTrue(hasText("Do user premium thing"));
|
||||
assertFalse(hasText("Do administration thing"));
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertFalse(wasDenied());
|
||||
|
||||
navigateToAdminPage();
|
||||
assertTrue(wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdminPermissions() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
login("admin", "admin");
|
||||
assertFalse(wasDenied());
|
||||
assertTrue(hasLink("User Premium"));
|
||||
assertTrue(hasLink("Administration"));
|
||||
assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
|
||||
assertTrue(hasText("urn:servlet-authz:page:main:actionForAdmin"));
|
||||
assertFalse(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
|
||||
|
||||
navigateToDynamicMenuPage();
|
||||
assertTrue(hasText("Do user thing"));
|
||||
assertTrue(hasText("Do administration thing"));
|
||||
assertFalse(hasText("Do user premium thing"));
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertTrue(wasDenied());
|
||||
|
||||
navigateToAdminPage();
|
||||
assertFalse(wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGrantPremiumAccessToUser() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertTrue(wasDenied());
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
if ("Premium Resource Permission".equals(policy.getName())) {
|
||||
policy.getConfig().put("applyPolicies", "[\"Any User Policy\"]");
|
||||
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
|
||||
}
|
||||
}
|
||||
|
||||
login("alice", "alice");
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertFalse(wasDenied());
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
if ("Premium Resource Permission".equals(policy.getName())) {
|
||||
policy.getConfig().put("applyPolicies", "[\"Only Premium User Policy\"]");
|
||||
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
|
||||
}
|
||||
}
|
||||
|
||||
login("alice", "alice");
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertTrue(wasDenied());
|
||||
|
||||
PolicyRepresentation onlyAlicePolicy = new PolicyRepresentation();
|
||||
|
||||
onlyAlicePolicy.setName("Temporary Premium Access Policy");
|
||||
onlyAlicePolicy.setType("user");
|
||||
HashMap<String, String> config = new HashMap<>();
|
||||
UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
|
||||
List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
|
||||
|
||||
assertFalse(users.isEmpty());
|
||||
|
||||
config.put("users", JsonSerialization.writeValueAsString(Arrays.asList(users.get(0).getId())));
|
||||
|
||||
onlyAlicePolicy.setConfig(config);
|
||||
getAuthorizationResource().policies().create(onlyAlicePolicy);
|
||||
|
||||
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
|
||||
if ("Premium Resource Permission".equals(policy.getName())) {
|
||||
policy.getConfig().put("applyPolicies", "[\"Temporary Premium Access Policy\"]");
|
||||
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
|
||||
}
|
||||
}
|
||||
|
||||
login("alice", "alice");
|
||||
|
||||
navigateToUserPremiumPage();
|
||||
assertFalse(wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGrantAdministrativePermissions() throws Exception {
|
||||
try {
|
||||
this.deployer.deploy(RESOURCE_SERVER_ID);
|
||||
|
||||
login("jdoe", "jdoe");
|
||||
|
||||
navigateToAdminPage();
|
||||
assertTrue(wasDenied());
|
||||
|
||||
RealmResource realmResource = realmsResouce().realm(REALM_NAME);
|
||||
UsersResource usersResource = realmResource.users();
|
||||
List<UserRepresentation> users = usersResource.search("jdoe", null, null, null, null, null);
|
||||
|
||||
assertFalse(users.isEmpty());
|
||||
|
||||
UserResource userResource = usersResource.get(users.get(0).getId());
|
||||
|
||||
RoleRepresentation adminRole = realmResource.roles().get("admin").toRepresentation();
|
||||
userResource.roles().realmLevel().add(Arrays.asList(adminRole));
|
||||
|
||||
login("jdoe", "jdoe");
|
||||
|
||||
navigateToAdminPage();
|
||||
assertFalse(wasDenied());
|
||||
} finally {
|
||||
this.deployer.undeploy(RESOURCE_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasLink(String text) {
|
||||
return getLink(text) != null;
|
||||
}
|
||||
|
||||
private boolean hasText(String text) {
|
||||
return this.driver.getPageSource().contains(text);
|
||||
}
|
||||
|
||||
private WebElement getLink(String text) {
|
||||
return this.driver.findElement(By.xpath("//a[text() = '" + text + "']"));
|
||||
}
|
||||
|
||||
private void importResourceServerSettings() throws FileNotFoundException {
|
||||
getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class));
|
||||
}
|
||||
|
||||
private AuthorizationResource getAuthorizationResource() throws FileNotFoundException {
|
||||
return getClientResource(RESOURCE_SERVER_ID).authorization();
|
||||
}
|
||||
|
||||
private ClientResource getClientResource(String clientId) {
|
||||
ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
|
||||
ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
|
||||
return clients.get(resourceServer.getId());
|
||||
}
|
||||
|
||||
private void logOut() {
|
||||
navigateTo();
|
||||
By by = By.xpath("//a[text() = 'Sign Out']");
|
||||
WaitUtils.waitUntilElement(by);
|
||||
this.driver.findElement(by).click();
|
||||
pause(500);
|
||||
}
|
||||
|
||||
private void login(String username, String password) throws InterruptedException {
|
||||
navigateTo();
|
||||
Thread.sleep(2000);
|
||||
if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
|
||||
Thread.sleep(2000);
|
||||
logOut();
|
||||
navigateTo();
|
||||
}
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
this.loginPage.form().login(username, password);
|
||||
}
|
||||
|
||||
private void navigateTo() {
|
||||
this.driver.navigate().to(getResourceServerUrl());
|
||||
WaitUtils.waitUntilElement(By.xpath("//a[text() = 'Dynamic Menu']"));
|
||||
}
|
||||
|
||||
private boolean wasDenied() {
|
||||
return this.driver.getPageSource().contains("You can not access this resource.");
|
||||
}
|
||||
|
||||
private URL getResourceServerUrl() {
|
||||
try {
|
||||
return new URL(this.appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("Could not obtain resource server url.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void navigateToDynamicMenuPage() {
|
||||
navigateTo();
|
||||
getLink("Dynamic Menu").click();
|
||||
}
|
||||
|
||||
private void navigateToUserPremiumPage() {
|
||||
navigateTo();
|
||||
getLink("User Premium").click();
|
||||
}
|
||||
|
||||
private void navigateToAdminPage() {
|
||||
navigateTo();
|
||||
getLink("Administration").click();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.keycloak.testsuite.adapter.servlet;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UseServletFilter;
|
||||
|
||||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
|
||||
@UseServletFilter(filterName = "saml-filter", filterClass = "org.keycloak.adapters.saml.servlet.SamlFilter")
|
||||
public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLServletsAdapterTest {
|
||||
|
||||
@Before
|
||||
public void checkRoles() {
|
||||
badClientSalesPostSigServletPage.checkRoles(true);
|
||||
badRealmSalesPostSigServletPage.checkRoles(true);
|
||||
employeeSigServletPage.checkRoles(true);
|
||||
employeeSigFrontServletPage.checkRoles(true);
|
||||
salesMetadataServletPage.checkRoles(true);
|
||||
salesPostServletPage.checkRoles(true);
|
||||
salesPostEncServletPage.checkRoles(true);
|
||||
salesPostSigServletPage.checkRoles(true);
|
||||
salesPostPassiveServletPage.checkRoles(true);
|
||||
salesPostSigEmailServletPage.checkRoles(true);
|
||||
salesPostSigPersistentServletPage.checkRoles(true);
|
||||
salesPostSigTransientServletPage.checkRoles(true);
|
||||
employee2ServletPage.navigateTo();
|
||||
|
||||
//using endpoint instead of query param because we are not able to put query param to IDP initiated login
|
||||
testRealmLoginPage.form().login(bburkeUser);
|
||||
employee2ServletPage.checkRolesEndPoint();
|
||||
employee2ServletPage.logout();
|
||||
|
||||
forbiddenIfNotAuthenticated = false;
|
||||
}
|
||||
|
||||
@After
|
||||
public void uncheckRoles() {
|
||||
badClientSalesPostSigServletPage.checkRoles(false);
|
||||
badRealmSalesPostSigServletPage.checkRoles(false);
|
||||
employee2ServletPage.checkRoles(false);
|
||||
employeeSigServletPage.checkRoles(false);
|
||||
employeeSigFrontServletPage.checkRoles(false);
|
||||
salesMetadataServletPage.checkRoles(false);
|
||||
salesPostServletPage.checkRoles(false);
|
||||
salesPostEncServletPage.checkRoles(false);
|
||||
salesPostSigServletPage.checkRoles(false);
|
||||
salesPostPassiveServletPage.checkRoles(false);
|
||||
salesPostSigEmailServletPage.checkRoles(false);
|
||||
salesPostSigPersistentServletPage.checkRoles(false);
|
||||
salesPostSigTransientServletPage.checkRoles(false);
|
||||
}
|
||||
}
|
|
@ -50,46 +50,48 @@ import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
|||
*/
|
||||
public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||
@Page
|
||||
private BadClientSalesPostSigServlet badClientSalesPostSigServletPage;
|
||||
protected BadClientSalesPostSigServlet badClientSalesPostSigServletPage;
|
||||
|
||||
@Page
|
||||
private BadRealmSalesPostSigServlet badRealmSalesPostSigServletPage;
|
||||
protected BadRealmSalesPostSigServlet badRealmSalesPostSigServletPage;
|
||||
|
||||
@Page
|
||||
private Employee2Servlet employee2ServletPage;
|
||||
protected Employee2Servlet employee2ServletPage;
|
||||
|
||||
@Page
|
||||
private EmployeeSigServlet employeeSigServletPage;
|
||||
protected EmployeeSigServlet employeeSigServletPage;
|
||||
|
||||
@Page
|
||||
private EmployeeSigFrontServlet employeeSigFrontServletPage;
|
||||
protected EmployeeSigFrontServlet employeeSigFrontServletPage;
|
||||
|
||||
@Page
|
||||
private SalesMetadataServlet salesMetadataServletPage;
|
||||
protected SalesMetadataServlet salesMetadataServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostServlet salesPostServletPage;
|
||||
protected SalesPostServlet salesPostServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostEncServlet salesPostEncServletPage;
|
||||
protected SalesPostEncServlet salesPostEncServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostPassiveServlet salesPostPassiveServletPage;
|
||||
protected SalesPostPassiveServlet salesPostPassiveServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostSigServlet salesPostSigServletPage;
|
||||
protected SalesPostSigServlet salesPostSigServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostSigEmailServlet salesPostSigEmailServletPage;
|
||||
protected SalesPostSigEmailServlet salesPostSigEmailServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostSigPersistentServlet salesPostSigPersistentServletPage;
|
||||
protected SalesPostSigPersistentServlet salesPostSigPersistentServletPage;
|
||||
|
||||
@Page
|
||||
private SalesPostSigTransientServlet salesPostSigTransientServletPage;
|
||||
protected SalesPostSigTransientServlet salesPostSigTransientServletPage;
|
||||
|
||||
@Page
|
||||
private SAMLIDPInitiatedLogin samlidpInitiatedLogin;
|
||||
protected SAMLIDPInitiatedLogin samlidpInitiatedLogin;
|
||||
|
||||
protected boolean forbiddenIfNotAuthenticated = true;
|
||||
|
||||
@Deployment(name = BadClientSalesPostSigServlet.DEPLOYMENT_NAME)
|
||||
protected static WebArchive badClientSalesPostSig() {
|
||||
|
@ -196,7 +198,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
waitUntilElement(By.xpath("//body")).text().contains("principal=bburke");
|
||||
}
|
||||
|
||||
private void testSuccessfulAndUnauthorizedLogin(SAMLServletWithLogout page, Login loginPage) {
|
||||
private void testSuccessfulAndUnauthorizedLogin(SAMLServlet page, Login loginPage) {
|
||||
assertSuccessfulLogin(page, bburkeUser, loginPage);
|
||||
page.logout();
|
||||
assertForbiddenLogin(page, "unauthorized", "password", loginPage);
|
||||
|
@ -223,7 +225,6 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
assertForbidden(employee2ServletPage);
|
||||
assertForbidden(employeeSigFrontServletPage);
|
||||
assertForbidden(salesPostSigPersistentServletPage);
|
||||
|
||||
salesPostServletPage.logout();
|
||||
}
|
||||
|
||||
|
@ -243,8 +244,12 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
|
||||
|
||||
salesPostPassiveServletPage.navigateTo();
|
||||
if (forbiddenIfNotAuthenticated) {
|
||||
waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
|
||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().equals(""));
|
||||
} else {
|
||||
waitUntilElement(By.xpath("//body")).text().contains("principal=null");
|
||||
}
|
||||
|
||||
salesPostSigEmailServletPage.navigateTo();
|
||||
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||
|
@ -320,9 +325,13 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
public void salesPostPassiveTest() {
|
||||
salesPostPassiveServletPage.navigateTo();
|
||||
|
||||
if (forbiddenIfNotAuthenticated) {
|
||||
waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
|
||||
//Different 403 status page on EAP and Wildfly
|
||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().equals(""));
|
||||
} else {
|
||||
waitUntilElement(By.xpath("//body")).text().contains("principal=null");
|
||||
}
|
||||
|
||||
assertSuccessfulLogin(salesPostServletPage, bburkeUser, testRealmSAMLPostLoginPage);
|
||||
|
||||
|
@ -331,9 +340,13 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
salesPostPassiveServletPage.logout();
|
||||
salesPostPassiveServletPage.navigateTo();
|
||||
|
||||
if (forbiddenIfNotAuthenticated) {
|
||||
waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
|
||||
//Different 403 status page on EAP and Wildfly
|
||||
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().equals(""));
|
||||
} else {
|
||||
waitUntilElement(By.xpath("//body")).text().contains("principal=null");
|
||||
}
|
||||
assertForbiddenLogin(salesPostServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage);
|
||||
assertForbidden(salesPostPassiveServletPage);
|
||||
|
||||
|
|
|
@ -147,8 +147,7 @@ public class CredentialsTest extends AbstractClientTest {
|
|||
// Get the certificate - to make sure cert was properly updated, and privateKey is null
|
||||
cert = certRsc.getKeyInfo();
|
||||
assertEquals("cert properly set", certificate2, cert.getCertificate());
|
||||
// TODO: KEYCLOAK-2981
|
||||
//assertNull("privateKey nullified", cert.getPrivateKey());
|
||||
assertNull("privateKey nullified", cert.getPrivateKey());
|
||||
|
||||
// Re-upload the private key
|
||||
certRsc.uploadJks(keyCertForm);
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package org.keycloak.testsuite.broker;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -9,7 +17,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||
|
@ -17,15 +24,12 @@ import org.keycloak.testsuite.pages.ErrorPage;
|
|||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jgroups.util.Util.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.TimeoutException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
|
||||
public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
||||
|
||||
|
@ -128,17 +132,6 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
return identityProviderRepresentation;
|
||||
}
|
||||
|
||||
private void waitForPage(String title) {
|
||||
long startAt = System.currentTimeMillis();
|
||||
|
||||
while (!driver.getTitle().toLowerCase().contains(title)
|
||||
&& System.currentTimeMillis() - startAt < 200) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ignore) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logInAsUserInIDP() {
|
||||
driver.navigate().to(getAccountUrl(consumerRealmName()));
|
||||
|
@ -146,9 +139,7 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
log.debug("Clicking social " + getIDPAlias());
|
||||
accountLoginPage.clickSocial(getIDPAlias());
|
||||
|
||||
if (!driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/")) {
|
||||
log.debug("Not on provider realm page, url: " + driver.getCurrentUrl());
|
||||
}
|
||||
waitForPage("log in to");
|
||||
|
||||
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||
driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/"));
|
||||
|
@ -166,9 +157,11 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
updateAccountInformationPage.updateAccountInformation("Firstname", "Lastname");
|
||||
|
||||
UsersResource consumerUsers = adminClient.realm(consumerRealmName()).users();
|
||||
Assert.assertTrue("There must be at least one user", consumerUsers.count() > 0);
|
||||
|
||||
List<UserRepresentation> users = consumerUsers.search("", 0, 5);
|
||||
int userCount = consumerUsers.count();
|
||||
Assert.assertTrue("There must be at least one user", userCount > 0);
|
||||
|
||||
List<UserRepresentation> users = consumerUsers.search("", 0, userCount);
|
||||
|
||||
boolean isUserFound = false;
|
||||
for (UserRepresentation user : users) {
|
||||
|
@ -195,11 +188,12 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
log.debug("Clicking social " + getIDPAlias());
|
||||
accountLoginPage.clickSocial(getIDPAlias());
|
||||
|
||||
waitForPage("log in to");
|
||||
|
||||
Assert.assertTrue("Driver should be on the provider realm page right now", driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/"));
|
||||
|
||||
accountLoginPage.login(getUserLogin(), getUserPassword());
|
||||
|
||||
System.out.println(driver.getPageSource());
|
||||
assertEquals(accountPage.buildUri().toASCIIString().replace("master", "consumer") + "/", driver.getCurrentUrl());
|
||||
|
||||
assertEquals(userCount, adminClient.realm(consumerRealmName()).users().count());
|
||||
|
@ -219,18 +213,40 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
driver.navigate().to(getAuthRoot()
|
||||
+ "/auth/realms/" + providerRealmName()
|
||||
+ "/protocol/" + "openid-connect"
|
||||
+ "/logout");
|
||||
+ "/logout?redirect_uri=" + encodeUrl(getAccountUrl(providerRealmName())));
|
||||
|
||||
driver.navigate().to(getAccountUrl(consumerRealmName()));
|
||||
|
||||
try {
|
||||
waitForPage("log in to");
|
||||
} catch (TimeoutException e) {
|
||||
log.debug(driver.getTitle());
|
||||
log.debug(driver.getPageSource());
|
||||
Assert.fail("Timeout while waiting for login page");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
waitForElementEnabled("login");
|
||||
} catch (TimeoutException e) {
|
||||
Assert.fail("Timeout while waiting for login element enabled");
|
||||
}
|
||||
|
||||
accountLoginPage.login(getUserLogin(), "invalid");
|
||||
accountLoginPage.login(getUserLogin(), "invalid");
|
||||
accountLoginPage.login(getUserLogin(), "invalid");
|
||||
}
|
||||
|
||||
assertEquals("Invalid username or password.", accountLoginPage.getError());
|
||||
|
||||
accountLoginPage.clickSocial(getIDPAlias());
|
||||
|
||||
try {
|
||||
waitForPage("log in to");
|
||||
} catch (TimeoutException e) {
|
||||
log.debug(driver.getTitle());
|
||||
log.debug(driver.getPageSource());
|
||||
Assert.fail("Timeout while waiting for login page");
|
||||
}
|
||||
|
||||
Assert.assertTrue("Driver should be on the provider realm page right now", driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/"));
|
||||
|
||||
accountLoginPage.login(getUserLogin(), getUserPassword());
|
||||
|
@ -238,24 +254,17 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
assertEquals("Account is disabled, contact admin.", errorPage.getError());
|
||||
}
|
||||
|
||||
protected void testSingleLogout() {
|
||||
private void testSingleLogout() {
|
||||
log.debug("Testing single log out");
|
||||
|
||||
driver.navigate().to(getAccountUrl(providerRealmName()));
|
||||
|
||||
Assert.assertTrue("Should be logged in the account page", driver.getTitle().endsWith("Account Management"));
|
||||
|
||||
String encodedAccount;
|
||||
try {
|
||||
encodedAccount = URLEncoder.encode(getAccountUrl(providerRealmName()), "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
encodedAccount = getAccountUrl(providerRealmName());
|
||||
}
|
||||
|
||||
driver.navigate().to(getAuthRoot()
|
||||
+ "/auth/realms/" + providerRealmName()
|
||||
+ "/protocol/" + "openid-connect"
|
||||
+ "/logout?redirect_uri=" + encodedAccount);
|
||||
+ "/logout?redirect_uri=" + encodeUrl(getAccountUrl(providerRealmName())));
|
||||
|
||||
waitForPage("log in to " + providerRealmName());
|
||||
|
||||
|
@ -274,4 +283,45 @@ public abstract class AbstractBrokerTest extends AbstractKeycloakTest {
|
|||
private String getAccountPasswordUrl(String realmName) {
|
||||
return getAuthRoot() + "/auth/realms/" + realmName + "/account/password";
|
||||
}
|
||||
|
||||
private void waitForPage(final String title) {
|
||||
WebDriverWait wait = new WebDriverWait(driver, 5);
|
||||
|
||||
ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() {
|
||||
@Override
|
||||
public Boolean apply(WebDriver input) {
|
||||
return input.getTitle().toLowerCase().contains(title);
|
||||
}
|
||||
};
|
||||
|
||||
wait.until(condition);
|
||||
}
|
||||
|
||||
private void waitForElementEnabled(final String elementName) {
|
||||
WebDriverWait wait = new WebDriverWait(driver, 5);
|
||||
|
||||
ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() {
|
||||
@Override
|
||||
public Boolean apply(WebDriver input) {
|
||||
List<WebElement> elements = input.findElements(By.name(elementName));
|
||||
if (elements.size() == 0)
|
||||
return false;
|
||||
|
||||
return elements.get(0).isEnabled();
|
||||
}
|
||||
};
|
||||
|
||||
wait.until(condition);
|
||||
}
|
||||
|
||||
private String encodeUrl(String url) {
|
||||
String result;
|
||||
try {
|
||||
result = URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
result = url;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue