keycloak-aplcache
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 27(+16 -11)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java 2(+1 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java 2(+0 -2)
adapters/oidc/spring-security/pom.xml 13(+7 -6)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java 39(+30 -9)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java 28(+19 -9)
core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java 3(+3 -0)
core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java 3(+3 -0)
examples/authz/photoz/photoz-realm.json 21(+18 -3)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 1(+0 -1)
model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java 7(+4 -3)
model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java 22(+18 -4)
server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java 50(+37 -13)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java 27(+24 -3)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 18(+7 -11)
services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java 3(+2 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java 17(+14 -3)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html 2(+1 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js 7(+4 -3)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html 24(+12 -12)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html 14(+7 -7)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 1(+0 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json 15(+10 -5)
testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json 147(+147 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp 11(+11 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml 25(+25 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp 6(+6 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp 48(+48 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp 6(+6 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json 14(+14 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadClientSalesPostSigServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadRealmSalesPostSigServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/Employee2Servlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigFrontServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java 63(+61 -2)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesMetadataServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostPassiveServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigEmailServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigPersistentServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigTransientServlet.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLServlet.java 15(+14 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java 133(+73 -60)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/UseServletFilter.java 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java 55(+42 -13)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java 87(+87 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java 28(+9 -19)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java 25(+11 -14)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java 427(+412 -15)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java 345(+345 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java 54(+54 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 61(+37 -24)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java 3(+1 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java 130(+90 -40)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java 2(+0 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java 2(+0 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java 3(+3 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml 7(+1 -6)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPSAMLFilterAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyDefaultAuthzConfigAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java 33(+33 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflySAMLFilterAdapterTest.java 11(+11 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9SAMLFilterAdapterTest.java 12(+12 -0)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html 71(+62 -9)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html 22(+15 -7)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index 6b1fe19..0c0fc23 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -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());
+ }
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index e151f76..a776762 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -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;
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
index 5c26124..aa6d3d2 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
@@ -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()) {
adapters/oidc/spring-security/pom.xml 13(+7 -6)
diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml
index 415266b..f19cdf6 100755
--- a/adapters/oidc/spring-security/pom.xml
+++ b/adapters/oidc/spring-security/pom.xml
@@ -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>
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
index af5f322..c8a99fe 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
@@ -23,9 +23,12 @@ import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import java.util.Map;
+
import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles;
/**
@@ -47,23 +50,41 @@ public class RolePolicyProvider implements PolicyProvider {
@Override
public void evaluate(Evaluation evaluation) {
- EvaluationContext context = evaluation.getContext();
- String[] roleIds = getRoles(this.policy);
+ Map<String, Object>[] roleIds = getRoles(this.policy);
if (roleIds.length > 0) {
- Identity identity = context.getIdentity();
+ Identity identity = evaluation.getContext().getIdentity();
+
+ for (Map<String, Object> current : roleIds) {
+ RoleModel role = getCurrentRealm().getRoleById((String) current.get("id"));
- for (String roleId : roleIds) {
- RoleModel role = getCurrentRealm().getRoleById(roleId);
+ if (role != null) {
+ boolean hasRole = hasRole(identity, role);
- if (role != null && identity.hasRole(role.getName())) {
- evaluation.grant();
- break;
+ if (!hasRole && Boolean.valueOf(isRequired(current))) {
+ evaluation.deny();
+ return;
+ } else if (hasRole) {
+ evaluation.grant();
+ }
}
}
}
}
+ private boolean isRequired(Map<String, Object> current) {
+ return (boolean) current.getOrDefault("required", false);
+ }
+
+ private boolean hasRole(Identity identity, RoleModel role) {
+ String roleName = role.getName();
+ if (role.isClientRole()) {
+ ClientModel clientModel = getCurrentRealm().getClientById(role.getContainerId());
+ return identity.hasClientRole(clientModel.getClientId(), roleName);
+ }
+ return identity.hasRealmRole(roleName);
+ }
+
private RealmModel getCurrentRealm() {
return this.authorization.getKeycloakSession().getContext().getRealm();
}
@@ -72,4 +93,4 @@ public class RolePolicyProvider implements PolicyProvider {
public void close() {
}
-}
+}
\ No newline at end of file
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
index 598a41e..301bb7b 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
@@ -34,7 +34,9 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -81,11 +83,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
RoleModel removedRole = ((RoleRemovedEvent) event).getRole();
policyStore.findByType(getId()).forEach(policy -> {
- List<String> roles = new ArrayList<>();
-
- for (String roleId : getRoles(policy)) {
- if (!roleId.equals(removedRole.getId())) {
- roles.add(roleId);
+ List<Map> roles = new ArrayList<>();
+
+ for (Map<String,Object> role : getRoles(policy)) {
+ if (!role.get("id").equals(removedRole.getId())) {
+ Map updated = new HashMap();
+ updated.put("id", role.get("id"));
+ Object required = role.get("required");
+ if (required != null) {
+ updated.put("required", required);
+ }
+ roles.add(updated);
}
}
@@ -96,7 +104,9 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
});
policyStore.delete(policy.getId());
} else {
- policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
+ Map<String, String> config = policy.getConfig();
+ config.put("roles", JsonSerialization.writeValueAsString(roles));
+ policy.setConfig(config);
}
} catch (IOException e) {
throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e);
@@ -116,17 +126,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
return "role";
}
- static String[] getRoles(Policy policy) {
+ static Map<String, Object>[] getRoles(Policy policy) {
String roles = policy.getConfig().get("roles");
if (roles != null) {
try {
- return JsonSerialization.readValue(roles.getBytes(), String[].class);
+ return JsonSerialization.readValue(roles.getBytes(), Map[].class);
} catch (IOException e) {
throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e);
}
}
- return new String[]{};
+ return new Map[] {};
}
}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index b2c5757..9cf710a 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -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) {
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java
index dd21901..9a51031 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java
@@ -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() {
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
index 6ccac33..2776b0a 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.representations.idm.authorization;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URI;
@@ -39,12 +40,14 @@ public class ResourceRepresentation {
private String name;
private String uri;
private String type;
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<ScopeRepresentation> scopes;
@JsonProperty("icon_uri")
private String iconUri;
private ResourceOwnerRepresentation owner;
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PolicyRepresentation> policies;
/**
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
index 39aa9c7..3a1f252 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
@@ -34,6 +34,7 @@ public class ScopeRepresentation {
private String name;
private String iconUri;
private List<PolicyRepresentation> policies;
+ private List<ResourceRepresentation> resources;
/**
* Creates an instance.
@@ -94,6 +95,14 @@ public class ScopeRepresentation {
this.policies = policies;
}
+ public List<ResourceRepresentation> getResources() {
+ return this.resources;
+ }
+
+ public void setResources(List<ResourceRepresentation> resources) {
+ this.resources = resources;
+ }
+
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
index e9b470a..9614ca3 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
@@ -32,6 +32,8 @@ public class RoleRepresentation {
protected Boolean scopeParamRequired;
protected boolean composite;
protected Composites composites;
+ private Boolean clientRole;
+ private String containerId;
public static class Composites {
protected Set<String> realm;
@@ -122,4 +124,20 @@ public class RoleRepresentation {
public void setComposite(boolean composite) {
this.composite = composite;
}
+
+ public Boolean getClientRole() {
+ return clientRole;
+ }
+
+ public void setClientRole(Boolean clientRole) {
+ this.clientRole = clientRole;
+ }
+
+ public String getContainerId() {
+ return containerId;
+ }
+
+ public void setContainerId(String containerId) {
+ this.containerId = containerId;
+ }
}
diff --git a/examples/authz/hello-world-authz-service/hello-world-authz-service.json b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
index ea56e62..6e509c7 100644
--- a/examples/authz/hello-world-authz-service/hello-world-authz-service.json
+++ b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
@@ -1,4 +1,6 @@
{
+ "allowRemoteResourceManagement": false,
+ "policyEnforcementMode": "ENFORCING",
"resources": [
{
"name": "Default Resource",
@@ -8,22 +10,26 @@
],
"policies": [
{
- "name": "Only From Realm Policy",
+ "name": "Default Policy",
"description": "A policy that grants access only for users within this realm",
"type": "js",
+ "logic": "POSITIVE",
+ "decisionStrategy": "AFFIRMATIVE",
"config": {
"applyPolicies": "[]",
- "code": "var context = $evaluation.getContext();\n\n// using attributes from the evaluation context to obtain the realm\nvar contextAttributes = context.getAttributes();\nvar realmName = contextAttributes.getValue('kc.realm.name').asString(0);\n\n// using attributes from the identity to obtain the issuer\nvar identity = context.getIdentity();\nvar identityAttributes = identity.getAttributes();\nvar issuer = identityAttributes.getValue('iss').asString(0);\n\n// only users from the realm have access granted \nif (issuer.endsWith(realmName)) {\n $evaluation.grant();\n}"
+ "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
}
},
{
"name": "Default Permission",
"description": "A permission that applies to the default resource type",
"type": "resource",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"defaultResourceType": "urn:hello-world-authz-service:resources:default",
"default": "true",
- "applyPolicies": "[\"Only From Realm Policy\"]"
+ "applyPolicies": "[\"Default Policy\"]"
}
}
]
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
index 98f0856..203b6e2 100755
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
@@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl">
-<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
+<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
<div id="content-area" class="col-md-9" role="main">
<div id="content" ng-view/>
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 2990675..e58c5f5 100755
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
}
+
+ $scope.Identity = Identity;
});
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
$scope.profile = Profile.get();
});
-module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
+module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
$scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data;
});
$scope.deleteAlbum = function (album) {
- var newAlbum = new Album(album);
- newAlbum.$delete({id: album.id}, function () {
+ new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
index da78224..c0cc6e1 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
@@ -10,7 +10,7 @@
<td>
<ul>
<li data-ng-repeat="p in value">
- <a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
+ <a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
</li>
</ul>
</td>
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index bd5853e..78c252a 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -1,4 +1,4 @@
-<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
+<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/>
<br/>
@@ -15,7 +15,7 @@
</thead>
<tbody>
<tr data-ng-repeat="p in albums">
- <td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
+ <td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
</tr>
</tbody>
</table>
examples/authz/photoz/photoz-realm.json 21(+18 -3)
diff --git a/examples/authz/photoz/photoz-realm.json b/examples/authz/photoz/photoz-realm.json
index b3b2b81..b0aeb5d 100644
--- a/examples/authz/photoz/photoz-realm.json
+++ b/examples/authz/photoz/photoz-realm.json
@@ -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/*"
],
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 388c9e4..a5d7f16 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -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");
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 95fb58b..6849d07 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -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"]
}
]
},
diff --git a/examples/authz/photoz/photoz-restful-api-authz-service.json b/examples/authz/photoz/photoz-restful-api-authz-service.json
index e8d8862..455948c 100644
--- a/examples/authz/photoz/photoz-restful-api-authz-service.json
+++ b/examples/authz/photoz/photoz-restful-api-authz-service.json
@@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view"
},
{
- "name": "urn:photoz.com:scopes:album:create"
+ "name": "urn:photoz.com:scopes:album:delete"
},
{
- "name": "urn:photoz.com:scopes:album:delete"
+ "name": "urn:photoz.com:scopes:album:create"
}
]
},
@@ -44,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",
diff --git a/examples/authz/servlet-authz/servlet-authz-app-config.json b/examples/authz/servlet-authz/servlet-authz-app-config.json
index d5fb1cb..43ebde4 100644
--- a/examples/authz/servlet-authz/servlet-authz-app-config.json
+++ b/examples/authz/servlet-authz/servlet-authz-app-config.json
@@ -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\"}]"
}
},
{
diff --git a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
index 95365ea..364d887 100644
--- a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
@@ -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>
\ No newline at end of file
+<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>
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
index 6bdf96f..6e3271b 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
@@ -38,6 +38,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -99,7 +100,7 @@ public class CachedPolicyStore implements PolicyStore {
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
- return getDelegate().findByResourceServer(resourceServerId);
+ return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
@@ -179,12 +180,12 @@ public class CachedPolicyStore implements PolicyStore {
@Override
public List<Policy> findByType(String type) {
- return getDelegate().findByType(type);
+ return getDelegate().findByType(type).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
public List<Policy> findDependentPolicies(String id) {
- return getDelegate().findDependentPolicies(id);
+ return getDelegate().findDependentPolicies(id).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
private String getCacheKeyForPolicy(String policyId) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
index ee638f6..495df61 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
@@ -68,7 +68,21 @@ public class CachedResourceStore implements ResourceStore {
@Override
public void delete(String id) {
- this.cache.remove(getCacheKeyForResource(id));
+ List<CachedResource> removed = this.cache.remove(getCacheKeyForResource(id));
+
+ if (removed != null) {
+ CachedResource cachedResource = removed.get(0);
+ List<String> byOwner = this.cache.get(getResourceOwnerCacheKey(cachedResource.getOwner()));
+
+ if (byOwner != null) {
+ byOwner.remove(id);
+
+ if (byOwner.isEmpty()) {
+ this.cache.remove(getResourceOwnerCacheKey(cachedResource.getOwner()));
+ }
+ }
+ }
+
getDelegate().delete(id);
}
@@ -109,12 +123,12 @@ public class CachedResourceStore implements ResourceStore {
@Override
public List<Resource> findByResourceServer(String resourceServerId) {
- return getDelegate().findByResourceServer(resourceServerId);
+ return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
}
@Override
public List<Resource> findByScope(String... id) {
- return getDelegate().findByScope(id);
+ return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
}
@Override
@@ -283,7 +297,7 @@ public class CachedResourceStore implements ResourceStore {
return;
}
cached = new ArrayList<>();
- this.cache.put(getResourceOwnerCacheKey(resource.getOwner()), cached);
+ this.cache.put(cacheKey, cached);
}
if (cached != null && !cached.contains(resource.getId())) {
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
index 802989c..986d007 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -98,7 +98,7 @@ public class JPAResourceStore implements ResourceStore {
@Override
public List<Resource> findByScope(String... id) {
- Query query = entityManager.createQuery("from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
+ Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
query.setParameter("scopeIds", Arrays.asList(id));
diff --git a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
index 6b9ab90..1481095 100644
--- a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
@@ -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>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
index b3cfe97..9b01ace 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
@@ -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"/>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 4dbc50b..333ab76 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -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>
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index ce2bc51..a0a7b6c 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -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()]);
}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
index ebc9679..f16e6c3 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
@@ -19,6 +19,10 @@ package org.keycloak.authorization.identity;
import org.keycloak.authorization.attribute.Attributes;
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Predicate;
+
/**
* <p>Represents a security identity, which can be a person or non-person entity that was previously authenticated.
*
@@ -45,13 +49,53 @@ public interface Identity {
Attributes getAttributes();
/**
- * Indicates if this identity is granted with a role with the given <code>roleName</code>.
+ * Indicates if this identity is granted with a role (realm or client) with the given <code>roleName</code>.
*
* @param roleName the name of the role
*
* @return true if the identity has the given role. Otherwise, it returns false.
*/
default boolean hasRole(String roleName) {
- return getAttributes().containsValue("roles", roleName);
+ return hasRealmRole(roleName) || hasClientRole(roleName);
+ }
+
+ /**
+ * Indicates if this identity is granted with a realm role with the given <code>roleName</code>.
+ *
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasRealmRole(String roleName) {
+ return getAttributes().containsValue("kc.realm.roles", roleName);
+ }
+
+ /**
+ * Indicates if this identity is granted with a client role with the given <code>roleName</code>.
+ *
+ * @param clientId the client id
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasClientRole(String clientId, String roleName) {
+ return getAttributes().containsValue("kc.client." + clientId + ".roles", roleName);
+ }
+
+ /**
+ * Indicates if this identity is granted with a client role with the given <code>roleName</code>.
+ *
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasClientRole(String roleName) {
+ return getAttributes().toMap().entrySet().stream().filter(entry -> {
+ String key = entry.getKey();
+ if (key.startsWith("kc.client") && key.endsWith(".roles")) {
+ return getAttributes().containsValue(key, roleName);
+ }
+ return false;
+ }).findFirst().isPresent();
}
}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
index e2ef2f9..d5fa9cc 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -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());
- boolean hasScope = true;
+ if (scopes.isEmpty()) {
+ Set<Resource> resources = new HashSet<>();
- for (Scope givenScope : policy.getScopes()) {
- boolean hasGivenScope = false;
+ resources.addAll(policy.getResources());
- for (Scope scope : permission.getScopes()) {
- if (givenScope.getId().equals(scope.getId())) {
- hasGivenScope = true;
- break;
- }
+ for (Resource resource : resources) {
+ scopes.addAll(resource.getScopes());
}
- if (!hasGivenScope) {
+ if (!resources.isEmpty() && scopes.isEmpty()) {
return 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())) {
+ return true;
+ }
+ }
}
- return hasScope;
+ return false;
}
}
diff --git a/server-spi/src/main/java/org/keycloak/events/Errors.java b/server-spi/src/main/java/org/keycloak/events/Errors.java
index 0981806..ea2b887 100755
--- a/server-spi/src/main/java/org/keycloak/events/Errors.java
+++ b/server-spi/src/main/java/org/keycloak/events/Errors.java
@@ -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";
}
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
index e5230f9..8ff3664 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
@@ -17,11 +17,22 @@
package org.keycloak.migration.migrators;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.util.JsonSerialization;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
*
@@ -33,6 +44,7 @@ public class MigrateTo2_1_0 {
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateDefaultRequiredAction(realm);
+ migrateRolePolicies(realm, session);
}
}
@@ -46,4 +58,46 @@ public class MigrateTo2_1_0 {
otpAction.setName("Configure OTP");
}
+
+ // KEYCLOAK-3338: Changes to how role policy config is stored"
+ private void migrateRolePolicies(RealmModel realm, KeycloakSession session) {
+ AuthorizationProvider authorizationProvider = session.getProvider(AuthorizationProvider.class);
+ StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+ realm.getClients().forEach(clientModel -> {
+ ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getId());
+
+ if (resourceServer != null) {
+ policyStore.findByType("role").forEach(policy -> {
+ Map<String, String> config = policy.getConfig();
+ String roles = config.get("roles");
+ List roleConfig;
+
+ try {
+ roleConfig = JsonSerialization.readValue(roles, List.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Malformed configuration for role policy [" + policy.getName() + "].", e);
+ }
+
+ if (!roleConfig.isEmpty() && roleConfig.get(0) instanceof String) {
+ try {
+ config.put("roles", JsonSerialization.writeValueAsString(roleConfig.stream().map(new Function<String, Map>() {
+ @Override
+ public Map apply(String roleId) {
+ Map updated = new HashMap();
+
+ updated.put("id", roleId);
+
+ return updated;
+ }
+ }).collect(Collectors.toList())));
+ policy.setConfig(config);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to migrate role policy [" + policy.getName() + "].", e);
+ }
+ }
+ });
+ }
+ });
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index a972194..47692ac 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -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();
@@ -357,19 +362,19 @@ public interface RealmModel extends RoleContainerModel {
Set<String> getEventsListeners();
void setEventsListeners(Set<String> listeners);
-
+
Set<String> getEnabledEventTypes();
void setEnabledEventTypes(Set<String> enabledEventTypes);
-
+
boolean isAdminEventsEnabled();
void setAdminEventsEnabled(boolean enabled);
-
+
boolean isAdminEventsDetailsEnabled();
void setAdminEventsDetailsEnabled(boolean enabled);
-
+
ClientModel getMasterAdminClient();
void setMasterAdminClient(ClientModel client);
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index a3468df..f0c75b7 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -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;
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
index 47c9d5b..7918815 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index f4d8feb..67ae7d4 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -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 -> clientAccess.addRole(roleName));
+
+ ClientModel resourceServerClient = realm.getClientById(resourceServer.getClientId());
+ AccessToken.Access resourceServerAccess = accessToken.addAccess(resourceServerClient.getClientId());
+ resourceServerAccess.roles(new HashSet<>());
- userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> resourceAccess.addRole(roleName));
+ userModel.getClientRoleMappings(resourceServerClient).stream().map(RoleModel::getName).forEach(roleName -> resourceServerAccess.addRole(roleName));
}
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
index d2d623c..7c529e9 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -154,7 +154,11 @@ public class PolicyService {
}
policyStore.findDependentPolicies(id).forEach(dependentPolicy -> {
- dependentPolicy.removeAssociatedPolicy(policy);
+ if (dependentPolicy.getAssociatedPolicies().size() == 1) {
+ policyStore.delete(dependentPolicy.getId());
+ } else {
+ dependentPolicy.removeAssociatedPolicy(policy);
+ }
});
policyStore.delete(policy.getId());
@@ -271,7 +275,6 @@ public class PolicyService {
}
StoreFactory storeFactory = authorization.getStoreFactory();
- PolicyStore policyStore = storeFactory.getPolicyStore();
for (String scopeId : scopeIds) {
boolean hasScope = false;
diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
index 77fe01e..cccb38f 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
@@ -21,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;
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index 59ba844..567675f 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -17,8 +17,6 @@
*/
package org.keycloak.authorization.admin;
-import org.jboss.resteasy.plugins.providers.multipart.InputPart;
-import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.util.Models;
@@ -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);
- rep.getOwner().setId(null);
+ if (rep.getOwner().getId().equals(this.resourceServer.getClientId())) {
+ rep.setOwner(null);
+ } else {
+ rep.getOwner().setId(null);
+ }
rep.setId(null);
rep.setPolicies(null);
rep.getScopes().forEach(scopeRepresentation -> {
@@ -175,15 +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 (clientIdSeparator != -1) {
+ clientId = roleName.substring(0, clientIdSeparator);
+ roleName = roleName.substring(clientIdSeparator + 1);
+ }
- if (!roles.isEmpty()) {
- String roleNames = "";
+ RoleModel role;
- for (String role : roles.split(",")) {
- if (!roleNames.isEmpty()) {
- roleNames = roleNames + ",";
+ if (clientId == null) {
+ role = realm.getRole(roleName);
+ } else {
+ role = realm.getClientByClientId(clientId).getRole(roleName);
}
- role = role.replace("\"", "");
+ // 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);
+ }
- roleNames = roleNames + "\"" + this.realm.getRole(role).getId() + "\"";
- }
+ if (role == null) {
+ throw new RuntimeException("Error while importing configuration. Role [" + role + "] could not be found.");
+ }
- config.put("roles", "[" + roleNames + "]");
+ 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,123 +460,59 @@ public class ResourceServerService {
}
private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) {
- PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
-
- rep.setId(null);
- rep.setDependentPolicies(null);
-
- Map<String, String> config = rep.getConfig();
-
- String roles = config.get("roles");
-
- if (roles != null && !roles.isEmpty()) {
- roles = roles.replace("[", "");
- roles = roles.replace("]", "");
-
- if (!roles.isEmpty()) {
- String roleNames = "";
+ try {
+ PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
- for (String role : roles.split(",")) {
- if (!roleNames.isEmpty()) {
- roleNames = roleNames + ",";
- }
+ rep.setId(null);
+ rep.setDependentPolicies(null);
- role = role.replace("\"", "");
+ Map<String, String> config = rep.getConfig();
- roleNames = roleNames + "\"" + this.realm.getRoleById(role).getName() + "\"";
- }
+ String roles = config.get("roles");
- config.put("roles", "[" + roleNames + "]");
+ if (roles != null && !roles.isEmpty()) {
+ List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
+ config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
+ roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
+ return roleMap;
+ }).collect(Collectors.toList())));
}
- }
-
- String users = config.get("users");
- if (users != null) {
- users = users.replace("[", "");
- users = users.replace("]", "");
+ String users = config.get("users");
- if (!users.isEmpty()) {
+ if (users != null && !users.isEmpty()) {
UserFederationManager userManager = this.session.users();
- String userNames = "";
-
- for (String user : users.split(",")) {
- if (!userNames.isEmpty()) {
- userNames = userNames + ",";
- }
-
- user = user.replace("\"", "");
-
- userNames = userNames + "\"" + userManager.getUserById(user, this.realm).getUsername() + "\"";
- }
-
- config.put("users", "[" + userNames + "]");
+ List<String> userIds = JsonSerialization.readValue(users, List.class);
+ config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, this.realm).getUsername()).collect(Collectors.toList())));
}
- }
- String scopes = config.get("scopes");
-
- if (scopes != null && !scopes.isEmpty()) {
- scopes = scopes.replace("[", "");
- scopes = scopes.replace("]", "");
+ String scopes = config.get("scopes");
- if (!scopes.isEmpty()) {
+ if (scopes != null && !scopes.isEmpty()) {
ScopeStore scopeStore = storeFactory.getScopeStore();
- String scopeNames = "";
-
- for (String scope : scopes.split(",")) {
- if (!scopeNames.isEmpty()) {
- scopeNames = scopeNames + ",";
- }
-
- scope = scope.replace("\"", "");
-
- scopeNames = scopeNames + "\"" + scopeStore.findById(scope).getName() + "\"";
- }
-
- config.put("scopes", "[" + scopeNames + "]");
+ List<String> scopeIds = JsonSerialization.readValue(scopes, List.class);
+ config.put("scopes", JsonSerialization.writeValueAsString(scopeIds.stream().map(scopeId -> scopeStore.findById(scopeId).getName()).collect(Collectors.toList())));
}
- }
- String policyResources = config.get("resources");
-
- if (policyResources != null && !policyResources.isEmpty()) {
- policyResources = policyResources.replace("[", "");
- policyResources = policyResources.replace("]", "");
+ String policyResources = config.get("resources");
- if (!policyResources.isEmpty()) {
+ if (policyResources != null && !policyResources.isEmpty()) {
ResourceStore resourceStore = storeFactory.getResourceStore();
- String resourceNames = "";
-
- for (String resource : policyResources.split(",")) {
- if (!resourceNames.isEmpty()) {
- resourceNames = resourceNames + ",";
- }
-
- resource = resource.replace("\"", "");
-
- resourceNames = resourceNames + "\"" + resourceStore.findById(resource).getName() + "\"";
- }
-
- config.put("resources", "[" + resourceNames + "]");
+ List<String> resourceIds = JsonSerialization.readValue(policyResources, List.class);
+ config.put("resources", JsonSerialization.writeValueAsString(resourceIds.stream().map(resourceId -> resourceStore.findById(resourceId).getName()).collect(Collectors.toList())));
}
- }
- String policyNames = "";
- Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+ Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
- if (!associatedPolicies.isEmpty()) {
- for (Policy associatedPolicy : associatedPolicies) {
- if (!policyNames.isEmpty()) {
- policyNames = policyNames + ",";
- }
-
- policyNames = policyNames + "\"" + associatedPolicy.getName() + "\"";
+ if (!associatedPolicies.isEmpty()) {
+ config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList())));
}
- config.put("applyPolicies", "[" + policyNames + "]");
- }
+ rep.setAssociatedPolicies(null);
- return rep;
+ return rep;
+ } catch (Exception e) {
+ throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e);
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
index 60dc51e..7e9d66d 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
@@ -63,13 +63,18 @@ public final class Models {
scope.setId(model.getId());
scope.setName(model.getName());
scope.setIconUri(model.getIconUri());
- scope.setPolicies(new ArrayList<>());
- Set<Policy> policies = new HashSet<>();
+ StoreFactory storeFactory = authorizationProvider.getStoreFactory();
- policies.addAll(authorizationProvider.getStoreFactory().getPolicyStore().findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()));
+ scope.setResources(new ArrayList<>());
- for (Policy policyModel : policies) {
+ storeFactory.getResourceStore().findByScope(model.getId()).forEach(resource -> scope.getResources().add(toRepresentation(resource, resource.getResourceServer(), authorizationProvider)));
+
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+
+ scope.setPolicies(new ArrayList<>());
+
+ policyStore.findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()).forEach(policyModel -> {
PolicyRepresentation policy = new PolicyRepresentation();
policy.setId(policyModel.getId());
@@ -79,7 +84,7 @@ public final class Models {
if (!scope.getPolicies().contains(policy)) {
scope.getPolicies().add(policy);
}
- }
+ });
return scope;
}
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index 405675a..0fd3474 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -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());
}
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 5ddd431..c40c38a 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
+import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.util.JsonSerialization;
@@ -37,6 +38,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -53,61 +55,59 @@ public class KeycloakIdentity implements Identity {
}
public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
- this.accessToken = accessToken;
-
- if (this.accessToken == null) {
+ if (accessToken == null) {
throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
}
-
+ if (keycloakSession == null) {
+ throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN);
+ }
+ this.accessToken = accessToken;
this.keycloakSession = keycloakSession;
this.realm = keycloakSession.getContext().getRealm();
- HashMap<String, Collection<String>> attributes = new HashMap<>();
+ Map<String, Collection<String>> attributes = new HashMap<>();
try {
ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
Iterator<String> iterator = objectNode.fieldNames();
- List<String> roleNames = new ArrayList<>();
while (iterator.hasNext()) {
String fieldName = iterator.next();
JsonNode fieldValue = objectNode.get(fieldName);
List<String> values = new ArrayList<>();
- values.add(fieldValue.asText());
-
- if (fieldName.equals("realm_access")) {
- JsonNode grantedRoles = fieldValue.get("roles");
+ if (fieldValue.isArray()) {
+ Iterator<JsonNode> valueIterator = fieldValue.iterator();
- if (grantedRoles != null) {
- Iterator<JsonNode> rolesIt = grantedRoles.iterator();
+ while (valueIterator.hasNext()) {
+ values.add(valueIterator.next().asText());
+ }
+ } else {
+ String value = fieldValue.asText();
- while (rolesIt.hasNext()) {
- roleNames.add(rolesIt.next().asText());
- }
+ if (StringUtil.isNullOrEmpty(value)) {
+ continue;
}
+
+ values.add(value);
}
- if (fieldName.equals("resource_access")) {
- Iterator<JsonNode> resourceAccessIt = fieldValue.iterator();
+ if (!values.isEmpty()) {
+ attributes.put(fieldName, values);
+ }
+ }
- while (resourceAccessIt.hasNext()) {
- JsonNode grantedRoles = resourceAccessIt.next().get("roles");
+ AccessToken.Access realmAccess = accessToken.getRealmAccess();
- if (grantedRoles != null) {
- Iterator<JsonNode> rolesIt = grantedRoles.iterator();
+ if (realmAccess != null) {
+ attributes.put("kc.realm.roles", realmAccess.getRoles());
+ }
- while (rolesIt.hasNext()) {
- roleNames.add(rolesIt.next().asText());
- }
- }
- }
- }
+ Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
- attributes.put(fieldName, values);
+ if (resourceAccess != null) {
+ resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
}
-
- attributes.put("roles", roleNames);
} catch (Exception e) {
throw new RuntimeException("Error while reading attributes from security token.", e);
}
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
index ccc457d..e5986b7 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -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());
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index f3695d0..45fb6fe 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -28,6 +28,7 @@ import org.keycloak.authorization.protection.permission.PermissionService;
import org.keycloak.authorization.protection.permission.PermissionsService;
import org.keycloak.authorization.protection.resource.ResourceService;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ErrorResponseException;
@@ -48,16 +49,12 @@ public class ProtectionService {
@Path("/resource_set")
public Object resource() {
KeycloakIdentity identity = createIdentity();
-
- if (!identity.hasRole("uma_protection")) {
- throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
- }
-
- ResourceSetService resourceManager = new ResourceSetService(getResourceServer(identity), this.authorization, null);
+ ResourceServer resourceServer = getResourceServer(identity);
+ ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
- ResourceService resource = new ResourceService(getResourceServer(identity), identity, resourceManager, this.authorization);
+ ResourceService resource = new ResourceService(resourceServer, identity, resourceManager, this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -68,10 +65,6 @@ public class ProtectionService {
public Object permission() {
KeycloakIdentity identity = createIdentity();
- if (!identity.hasRole("uma_protection")) {
- throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
- }
-
PermissionService resource = new PermissionService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -83,10 +76,6 @@ public class ProtectionService {
public Object permissions() {
KeycloakIdentity identity = createIdentity();
- if (!identity.hasRole("uma_protection")) {
- throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
- }
-
PermissionsService resource = new PermissionsService(identity, getResourceServer(identity), this.authorization);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -95,7 +84,17 @@ public class ProtectionService {
}
private KeycloakIdentity createIdentity() {
- return new KeycloakIdentity(this.authorization.getKeycloakSession());
+ KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
+ ResourceServer resourceServer = getResourceServer(identity);
+ KeycloakSession keycloakSession = authorization.getKeycloakSession();
+ RealmModel realm = keycloakSession.getContext().getRealm();
+ ClientModel client = realm.getClientById(resourceServer.getClientId());
+
+ if (!identity.hasClientRole(client.getClientId(), "uma_protection")) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
+ }
+
+ return identity;
}
private ResourceServer getResourceServer(Identity identity) {
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index f2cce51..ebc57c2 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -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();
-
- permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
+ 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());
+ }
- for (Scope scope : scopes) {
- permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
+ 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;
-
- 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> permissions = new HashMap<>();
- Map<String, Permission> perms = new HashMap<>();
-
- permissions.forEach(permission -> {
- Permission evalPermission = perms.get(permission.getResourceSetId());
-
- if (evalPermission == null) {
- evalPermission = permission;
- perms.put(permission.getResourceSetId(), evalPermission);
+ 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();
- Set<String> permissionScopes = permission.getScopes();
+ if (resource != null) {
+ String resourceId = resource.getId();
+ String resourceName = resource.getName();
+ Permission evalPermission = permissions.get(resource.getId());
- if (permissionScopes != null && !permissionScopes.isEmpty()) {
- Set<String> scopes = evalPermission.getScopes();
-
- if (scopes == null) {
- scopes = new HashSet();
- evalPermission.setScopes(scopes);
+ if (evalPermission == null) {
+ evalPermission = new Permission(resourceId, resourceName, scopes);
+ permissions.put(resourceId, evalPermission);
}
- for (String scopeName : permissionScopes) {
- if (!scopes.contains(scopeName)) {
- scopes.add(scopeName);
+ if (scopes != null && !scopes.isEmpty()) {
+ Set<String> finalScopes = evalPermission.getScopes();
+
+ if (finalScopes == null) {
+ finalScopes = new HashSet();
+ evalPermission.setScopes(finalScopes);
+ }
+
+ 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());
}
}
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 214abb3..1add03d 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -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;
+ }
+ });
+
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 251cd8e..bf6fb88 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -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) {
+ } 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) {
+ } 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);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 895bd64..4f41596 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -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);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index cf8791e..66dee2e 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -892,7 +892,7 @@ public class LoginActionsService {
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event);
}
- }
+ }
if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
return context.getChallenge();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java
index 31b221b..1faf21e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java
@@ -49,6 +49,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -56,6 +57,8 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import static org.jboss.aesh.terminal.Key.e;
+
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@@ -279,8 +282,12 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
RoleModel adminRole = realm.getRole("admin");
+ Map role = new HashMap();
+
+ role.put("id", adminRole.getId());
+
try {
- config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
+ config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -352,10 +359,14 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
Policy policy = policyStore.create("Any User Policy", "role", resourceServer);
HashedMap config = new HashedMap();
RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
- RoleModel adminRole = realm.getRole("user");
+ RoleModel userRole = realm.getRole("user");
+
+ Map role = new HashMap();
+
+ role.put("id", userRole.getId());
try {
- config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
+ config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index f1bc7e9..dbbaaf4 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -70,6 +70,11 @@
<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>
<version>${arquillian-drone.version}</version>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
index 5ac24ea..bec9fae 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
@@ -19,7 +19,7 @@
<body data-ng-controller="TokenCtrl">
-<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
+<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
<div id="content-area" class="col-md-9" role="main">
<div id="content" ng-view/>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 2990675..e58c5f5 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
$scope.requestEntitlements = function () {
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
}
+
+ $scope.Identity = Identity;
});
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
$scope.profile = Profile.get();
});
-module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
+module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
$scope.albums = {};
$http.get(apiUrl + '/admin/album').success(function (data) {
$scope.albums = data;
});
$scope.deleteAlbum = function (album) {
- var newAlbum = new Album(album);
- newAlbum.$delete({id: album.id}, function () {
+ new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
index da78224..00f52d7 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
@@ -1,19 +1,19 @@
<h1>All Albums</h1>
<table class="table" data-ng-repeat="(key, value) in albums">
<thead>
- <tr>
- <th>{{key}}</th>
- </tr>
+ <tr>
+ <th>{{key}}</th>
+ </tr>
</thead>
<tbody>
- <tr>
- <td>
- <ul>
- <li data-ng-repeat="p in value">
- <a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
- </li>
- </ul>
- </td>
- </tr>
+ <tr>
+ <td>
+ <ul>
+ <li data-ng-repeat="p in value">
+ <a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
+ </li>
+ </ul>
+ </td>
+ </tr>
</tbody>
</table>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index bd5853e..e144d1b 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -1,4 +1,4 @@
-<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
+<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
<hr/>
<br/>
@@ -9,14 +9,14 @@
<span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span>
<table class="table" data-ng-show="albums.length > 0">
<thead>
- <tr>
- <th>Your Albums</th>
- </tr>
+ <tr>
+ <th>Your Albums</th>
+ </tr>
</thead>
<tbody>
- <tr data-ng-repeat="p in albums">
- <td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
- </tr>
+ <tr data-ng-repeat="p in albums">
+ <td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
+ </tr>
</tbody>
</table>
</div>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
index b3b2b81..b0aeb5d 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
@@ -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/*"
],
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 388c9e4..a5d7f16 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -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");
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 95fb58b..a3ac697 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -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"]
}
]
},
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
index cc8c8f8..6547d2f 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
@@ -21,10 +21,10 @@
"name": "urn:photoz.com:scopes:album:view"
},
{
- "name": "urn:photoz.com:scopes:album:create"
+ "name": "urn:photoz.com:scopes:album:delete"
},
{
- "name": "urn:photoz.com:scopes:album:delete"
+ "name": "urn:photoz.com:scopes:album:create"
}
]
},
@@ -44,12 +44,15 @@
"name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something",
"type": "drools",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
"mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession",
- "mavenArtifactGroupId": "org.keycloak.testsuite",
+ "mavenArtifactGroupId": "org.keycloak",
"moduleName": "PhotozAuthzOwnerPolicy",
+ "applyPolicies": "[]",
"scannerPeriod": "1",
"scannerPeriodUnit": "Hours"
}
@@ -58,16 +61,22 @@
"name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something",
"type": "role",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
"config": {
- "roles": "[\"admin\"]"
+ "applyPolicies": "[]",
+ "roles": "[{\"id\":\"admin\",\"required\":true}]"
}
},
{
"name": "Any User Policy",
- "description": "Defines that any user can do something",
+ "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",
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 391e0d8..fa4b0c1 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -20,5 +20,6 @@
<module>js-database</module>
<module>photoz</module>
<module>hello-world-authz-service</module>
+ <module>servlet-authz</module>
</modules>
</project>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
new file mode 100755
index 0000000..fc9f6b0
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
@@ -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>
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/README.md b/testsuite/integration-arquillian/test-apps/servlet-authz/README.md
new file mode 100644
index 0000000..f93acb5
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/README.md
@@ -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
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
new file mode 100644
index 0000000..43ebde4
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
@@ -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\"]"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
new file mode 100644
index 0000000..371e451
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
@@ -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"
+ }
+ ]
+}
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp
new file mode 100644
index 0000000..6f25023
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ <h2 style="color: red">You can not access this resource.</h2>
+ <%@include file="logout-include.jsp"%>
+ </body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp
new file mode 100755
index 0000000..3fbfca2
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp
@@ -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>
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp
new file mode 100644
index 0000000..21ef2ed
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp
@@ -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>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml
new file mode 100644
index 0000000..515ffa5
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml
@@ -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>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
new file mode 100644
index 0000000..5946cd6
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
@@ -0,0 +1,6 @@
+<html>
+<body>
+ <h2>Only Administrators can access this page.</h2>
+ <%@include file="../../logout-include.jsp"%>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
new file mode 100644
index 0000000..1473d22
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
@@ -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>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
new file mode 100644
index 0000000..9244f9c
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
@@ -0,0 +1,6 @@
+<html>
+<body>
+<h2>Only for premium users.</h2>
+<%@include file="../../logout-include.jsp"%>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000..7b362a7
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..14d0615
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
@@ -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>
diff --git a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
index 9621ae5..3e90544 100755
--- a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
+++ b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
@@ -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>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadClientSalesPostSigServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadClientSalesPostSigServlet.java
index e85d43e..163d3f1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadClientSalesPostSigServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadClientSalesPostSigServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadRealmSalesPostSigServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadRealmSalesPostSigServlet.java
index 08fd844..f4dcc0c 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadRealmSalesPostSigServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadRealmSalesPostSigServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/Employee2Servlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/Employee2Servlet.java
index 391c122..4257c2f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/Employee2Servlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/Employee2Servlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigFrontServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigFrontServlet.java
index ec21b58..0e7886c 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigFrontServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigFrontServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigServlet.java
index 24d92d0..910ef46 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index 09bf6a9..0e07157 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -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);
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesMetadataServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesMetadataServlet.java
index 730ed2e..93f2043 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesMetadataServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesMetadataServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
index 892848a..874b1e8 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostPassiveServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostPassiveServlet.java
index 47afaae..a5879c0 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostPassiveServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostPassiveServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostServlet.java
index e14acd4..cd9ea11 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigEmailServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigEmailServlet.java
index 789469e..77c68f1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigEmailServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigEmailServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigPersistentServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigPersistentServlet.java
index 059202f..5ccb96f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigPersistentServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigPersistentServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigServlet.java
index 77c57f1..b4ab9bb 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigTransientServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigTransientServlet.java
index 17ca8a8..697ac83 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigTransientServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostSigTransientServlet.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
index 6a07594..58feae3 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
@@ -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>
-* @version $Revision: 1 $
-*/
-public class SendUsernameServlet extends HttpServlet {
-
- public static Principal sentPrincipal;
- public static List<String> checkRoles;
-
- @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;
- }
- }
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author mhajas
+ * @version $Revision: 1 $
+ */
+@Path("/")
+public class SendUsernameServlet {
+ private static boolean checkRoles = false;
+
+ @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();
}
- 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 Response.ok(getOutput(), MediaType.TEXT_PLAIN).build();
}
- @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);
- }
- }
+
+ @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");
}
- 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());
+
+ 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;
+
+ return output + principal.getName();
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/UseServletFilter.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/UseServletFilter.java
new file mode 100644
index 0000000..dc0b3f4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/UseServletFilter.java
@@ -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 "";
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
index a63595e..68d7183 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
@@ -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.*;
;
@@ -125,7 +125,7 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
adapterConfig.setAuthServerUrl(getAuthServerContextRoot() + "/auth");
adapterConfig.setRealmKey(REALM_KEY);
}
-
+
if ("true".equals(System.getProperty("app.server.ssl.required"))) {
adapterConfig.setSslRequired("all");
}
@@ -155,17 +155,46 @@ 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());
-
+ 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);
- archive.add(new StringAsset((webXmlContent)), WEBXML_PATH);
- } catch (IOException ex) {
- throw new RuntimeException("Cannot load web.xml from archive.");
+ //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.");
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java
new file mode 100644
index 0000000..808a383
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java
@@ -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;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index a4f7257..5f8a51d 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -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();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java
index 82b3ec4..4666674 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java
@@ -59,31 +59,28 @@ public abstract class AbstractDefaultAuthzConfigAdapterTest extends AbstractExam
@Test
public void testDefaultAuthzConfig() throws Exception {
- configureAuthorizationServices();
- deploy();
- navigateToResourceServer();
- login();
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+ configureAuthorizationServices();
- assertTrue(this.driver.getPageSource().contains("Your permissions are"));
- assertTrue(this.driver.getPageSource().contains("Default Resource"));
- }
+ login();
- private void login() {
- this.loginPage.form().login("alice", "alice");
+ assertTrue(this.driver.getPageSource().contains("Your permissions are"));
+ assertTrue(this.driver.getPageSource().contains("Default Resource"));
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
}
- 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);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 32f23be..9a0fb26 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -16,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());
}
}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
new file mode 100644
index 0000000..7f61556
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
@@ -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();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
new file mode 100644
index 0000000..0df65a4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index cef65dc..ddc23b7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -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();
- waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().equals(""));
+ 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();
- 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(""));
+ 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();
- 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(""));
+ 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);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java
index a9d038e..0b2ffaf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java
@@ -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);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
index a2a16b4..29f796f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
@@ -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()));
- accountLoginPage.login(getUserLogin(), "invalid");
- accountLoginPage.login(getUserLogin(), "invalid");
- accountLoginPage.login(getUserLogin(), "invalid");
+ 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");
+ }
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;
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java
index 7a8fb9c..cdacaa7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java
@@ -1,6 +1,5 @@
package org.keycloak.testsuite.broker;
-import org.junit.Ignore;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@@ -13,7 +12,6 @@ import java.util.Map;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
-@Ignore
public class KcSamlBrokerTest extends AbstractBrokerTest {
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
index 1a2eca8..128d1e0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
@@ -1,6 +1,5 @@
package org.keycloak.testsuite.broker;
-import org.junit.Ignore;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -11,7 +10,6 @@ import java.util.Map;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
-@Ignore
public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 98a3fe6..78d20d9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -314,6 +314,7 @@ public class ResetPasswordTest extends TestRealmKeycloakTest {
assertTrue(updatePasswordPage.isCurrent());
assertEquals(error, updatePasswordPage.getError());
+ events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
}
@Test
@@ -544,6 +545,8 @@ public class ResetPasswordTest extends TestRealmKeycloakTest {
assertEquals("Invalid password: minimum length 8.", resetPasswordPage.getErrorMessage());
+ events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
+
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml
index 6f4b9c2..6b5322d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml
@@ -27,7 +27,7 @@
<module name="org.codehaus.jackson.jackson-mapper-asl" />
<module name="org.bouncycastle" />
<module name="org.jboss.xnio" />
-
+
</dependencies>
</deployment>
</jboss-deployment-structure>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml
index 44aa653..4207f91 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml
@@ -23,13 +23,8 @@
<module-name>%CONTEXT_PATH%</module-name>
- <servlet>
- <servlet-name>Servlet</servlet-name>
- <servlet-class>org.keycloak.testsuite.adapter.servlet.SendUsernameServlet</servlet-class>
- </servlet>
-
<servlet-mapping>
- <servlet-name>Servlet</servlet-name>
+ <servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPSAMLFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPSAMLFilterAdapterTest.java
new file mode 100644
index 0000000..bf739a7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPSAMLFilterAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSAMLFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.arquillian.annotation.UseServletFilter;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-eap")
+public class EAPSAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java
new file mode 100644
index 0000000..627e5f3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSAMLFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.arquillian.annotation.UseServletFilter;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-eap6")
+public class EAPSAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
new file mode 100644
index 0000000..d50cf29
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.RunAsClient;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractServletAuthzAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflySAMLFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflySAMLFilterAdapterTest.java
new file mode 100644
index 0000000..d5e837d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflySAMLFilterAdapterTest.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSAMLFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-wildfly")
+public class WildflySAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9SAMLFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9SAMLFilterAdapterTest.java
new file mode 100644
index 0000000..230cafa
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9SAMLFilterAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSAMLFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.arquillian.annotation.UseServletFilter;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-wildfly9")
+public class Wildfly9SAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index b8c715f..7ae36b2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -287,6 +287,12 @@
<version>${project.version}</version>
<type>war</type>
</artifactItem>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>servlet-authz-app</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ </artifactItem>
</artifactItems>
<outputDirectory>${examples.home}</outputDirectory>
<overWriteIfNewer>true</overWriteIfNewer>
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 1489009..0db29a3 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -173,6 +173,7 @@
<browser>${browser}</browser>
<firefox_binary>${firefox_binary}</firefox_binary>
+ <project.version>${project.version}</project.version>
</systemPropertyVariables>
<properties>
<property>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index fbfe64b..1f39b06 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -953,6 +953,7 @@ authz-no-resources=No resources
authz-result=Result
authz-authorization-services-enabled=Authorization Enabled
authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client
+authz-required=Required
# Authz Settings
authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server.
@@ -1016,7 +1017,9 @@ authz-select-a-policy=Select a policy
# Authz Role Policy Detail
authz-add-role-policy=Add Role Policy
authz-no-roles-assigned=No roles assigned.
-authz-policy-role-roles.tooltip=Specifies which role(s) are allowed by this policy.
+authz-policy-role-realm-roles.tooltip=Specifies which *realm* role(s) are allowed by this policy.
+authz-policy-role-clients.tooltip=Selects a client in order to filter the client roles that can be applied to this policy.
+authz-policy-role-client-roles.tooltip=Specifies which *client* role(s) are allowed by this policy.
# Authz User Policy Detail
authz-add-user-policy=Add User Policy
@@ -1073,10 +1076,11 @@ authz-permission-scope-scope.tooltip=Specifies that this permission must be appl
# Authz Evaluation
authz-evaluation-identity-information=Identity Information
authz-evaluation-identity-information.tooltip=The available options to configure the identity information that will be used when evaluating policies.
-authz-evaluation-client.tooltip=Select the client making this authorization request.
+authz-evaluation-client.tooltip=Select the client making this authorization request. If not provided, authorization requests would be done based on the client you are in.
authz-evaluation-user.tooltip=Select an user whose identity is going to be used to query permissions from the server.
authz-evaluation-role.tooltip=Select the roles you want to associate with the selected user.
authz-evaluation-new=New Evaluation
+authz-evaluation-re-evaluate=Re-Evaluate
authz-evaluation-previous=Previous Evaluation
authz-evaluation-contextual-info=Contextual Information
authz-evaluation-contextual-info.tooltip=The available options to configure any contextual information that will be used when evaluating policies.
@@ -1090,3 +1094,6 @@ authz-evaluation-no-policies-resource=No policies were found for this resource.
authz-evaluation-result.tooltip=The overall result for this permission request.
authz-evaluation-scopes.tooltip=The requested scopes.
authz-evaluation-policies.tooltip=Details about which policies were evaluated and their decisions.
+authz-evaluation-authorization-data=Response
+authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request.
+authz-show-authorization-data=Show Authorization Data
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
index 680cb5b..d772f92 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
@@ -150,6 +150,14 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
client : client.id,
rsrid : $route.current.params.rsrid,
}, function(data) {
+ if (!data.scopes) {
+ data.scopes = [];
+ }
+
+ if (!data.policies) {
+ data.policies = [];
+ }
+
$scope.resource = angular.copy(data);
$scope.changed = false;
@@ -157,9 +165,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
$scope.resource.scopes[i] = $scope.resource.scopes[i].name;
}
- data = angular.copy($scope.resource);
-
- $scope.originalResource = data;
+ $scope.originalResource = angular.copy($scope.resource);
$scope.$watch('resource', function() {
if (!angular.equals($scope.resource, data)) {
@@ -237,6 +243,10 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo
ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) {
$scope.scopes = data;
});
+
+ $scope.createPolicy = function(scope) {
+ $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id});
+ }
});
});
@@ -534,7 +544,7 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro
}, realm, client, $scope);
});
-module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
+module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, $location, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
PolicyController.onInit({
getPolicyType : function() {
return "scope";
@@ -624,6 +634,12 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
newPolicy.decisionStrategy = 'UNANIMOUS';
newPolicy.config = {};
newPolicy.config.resources = '';
+
+ var scopeId = $location.search()['scpid'];
+
+ if (scopeId) {
+ newPolicy.config.scopes = [scopeId];
+ }
},
onCreate : function() {
@@ -712,7 +728,7 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route,
}, realm, client, $scope);
});
-module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, PolicyController, Role, RoleById) {
+module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, Client, ClientRole, PolicyController, Role, RoleById) {
PolicyController.onInit({
getPolicyType : function() {
return "role";
@@ -723,6 +739,10 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.roles = data;
});
+ Client.query({realm: $route.current.params.realm}, function (data) {
+ $scope.clients = data;
+ });
+
$scope.selectedRoles = [];
$scope.selectRole = function(role) {
@@ -732,10 +752,55 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
$scope.selectedRole = {};
$scope.selectedRoles.push(role);
+
+ var clientRoles = [];
+
+ if ($scope.clientRoles) {
+ for (i = 0; i < $scope.clientRoles.length; i++) {
+ if ($scope.clientRoles[i].id != role.id) {
+ clientRoles.push($scope.clientRoles[i]);
+ }
+ }
+ $scope.clientRoles = clientRoles;
+ }
}
- $scope.removeFromList = function(list, index) {
- list.splice(index, 1);
+ $scope.removeFromList = function(role) {
+ if ($scope.clientRoles && $scope.selectedClient && $scope.selectedClient.id == role.containerId) {
+ $scope.clientRoles.push(role);
+ }
+ var index = $scope.selectedRoles.indexOf(role);
+ if (index != -1) {
+ $scope.selectedRoles.splice(index, 1);
+ }
+ }
+
+ $scope.selectClient = function() {
+ if (!$scope.selectedClient) {
+ $scope.clientRoles = [];
+ return;
+ }
+ ClientRole.query({realm: $route.current.params.realm, client: $scope.selectedClient.id}, function(data) {
+ var roles = [];
+
+ for (j = 0; j < data.length; j++) {
+ var defined = false;
+
+ for (i = 0; i < $scope.selectedRoles.length; i++) {
+ if ($scope.selectedRoles[i].id == data[j].id) {
+ defined = true;
+ break;
+ }
+ }
+
+ if (!defined) {
+ data[j].container = {};
+ data[j].container.name = $scope.selectedClient.clientId;
+ roles.push(data[j]);
+ }
+ }
+ $scope.clientRoles = roles;
+ });
}
},
@@ -746,7 +811,18 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = eval(policy.config.roles);
for (i = 0; i < roles.length; i++) {
- RoleById.get({realm: $route.current.params.realm, role: roles[i]}, function(data) {
+ RoleById.get({realm: $route.current.params.realm, role: roles[i].id}, function(data) {
+ for (i = 0; i < roles.length; i++) {
+ if (roles[i].id == data.id) {
+ data.required = roles[i].required ? true : false;
+ }
+ }
+ for (i = 0; i < $scope.clients.length; i++) {
+ if ($scope.clients[i].id == data.containerId) {
+ data.container = {};
+ data.container.name = $scope.clients[i].clientId;
+ }
+ }
selectedRoles.push(data);
$scope.selectedRoles = angular.copy(selectedRoles);
});
@@ -764,7 +840,12 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) {
- roles.push($scope.selectedRoles[i].id);
+ var role = {};
+ role.id = $scope.selectedRoles[i].id;
+ if ($scope.selectedRoles[i].required) {
+ role.required = $scope.selectedRoles[i].required;
+ }
+ roles.push(role);
}
$scope.policy.config.roles = JSON.stringify(roles);
@@ -774,12 +855,35 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
var roles = [];
for (i = 0; i < $scope.selectedRoles.length; i++) {
- roles.push($scope.selectedRoles[i].id);
+ var role = {};
+ role.id = $scope.selectedRoles[i].id;
+ if ($scope.selectedRoles[i].required) {
+ role.required = $scope.selectedRoles[i].required;
+ }
+ roles.push(role);
}
$scope.policy.config.roles = JSON.stringify(roles);
}
}, realm, client, $scope);
+
+ $scope.hasRealmRole = function () {
+ for (i = 0; i < $scope.selectedRoles.length; i++) {
+ if (!$scope.selectedRoles[i].clientRole) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ $scope.hasClientRole = function () {
+ for (i = 0; i < $scope.selectedRoles.length; i++) {
+ if ($scope.selectedRoles[i].clientRole) {
+ return true;
+ }
+ }
+ return false;
+ }
});
module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) {
@@ -1225,6 +1329,18 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
}
}
+ $scope.reevaluate = function() {
+ if ($scope.authzRequest.entitlements) {
+ $scope.entitlements();
+ } else {
+ $scope.save();
+ }
+ }
+
+ $scope.showAuthzData = function() {
+ $scope.showRpt = true;
+ }
+
$scope.save = function() {
$scope.authzRequest.entitlements = false;
if ($scope.applyResourceType) {
@@ -1252,10 +1368,12 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
$scope.showResultTab = function() {
$scope.showResult = true;
+ $scope.showRpt = false;
}
$scope.showRequestTab = function() {
$scope.showResult = false;
+ $scope.showRpt = false;
}
User.query({realm: $route.current.params.realm}, function(data) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
index 9b0c199..8904bb3 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
@@ -49,31 +49,85 @@
<kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
- <label class="col-md-2 control-label" for="roles">{{:: 'roles' | translate}} <span class="required">*</span></label>
+ <label class="col-md-2 control-label" for="roles">{{:: 'realm-roles' | translate}} <span class="required">*</span></label>
- <div class="col-md-6">
+ <div class="col-md-4">
<select ui-select2="{ minimumInputLength: 1}" id="roles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
ng-options="role as role.name for role in roles" data-ng-required="selectedUsers.length == 0 && selectedRoles.length == 0">
</select>
</div>
- <kc-tooltip>{{:: 'authz-policy-role-roles.tooltip' | translate}}</kc-tooltip>
+ <kc-tooltip>{{:: 'authz-policy-role-realm-roles.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" style="margin-top: -15px;">
<label class="col-md-2 control-label"></label>
- <div class="col-sm-3">
+ <div class="col-sm-4" data-ng-show="hasRealmRole()">
<table class="table table-striped table-bordered">
<thead>
- <tr data-ng-hide="!selectedRoles.length">
- <th>{{:: 'name' | translate}}</th>
+ <tr>
+ <th class="col-sm-5">{{:: 'name' | translate}}</th>
+ <th>{{:: 'authz-required' | translate}}</th>
+ <th>{{:: 'actions' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="!role.clientRole">
+ <td>{{role.name}}</td>
+ <td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
+ <td class="kc-action-cell">
+ <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
+ </td>
+ </tr>
+ <tr data-ng-show="!selectedRoles.length">
+ <td class="text-muted" colspan="3">{{:: 'authz-no-roles-assigned' | translate}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="clients">{{:: 'clients' | translate}}</label>
+
+ <div class="col-md-4">
+ <select class="form-control" id="clients"
+ ng-model="selectedClient"
+ ng-change="selectClient()"
+ data-ng-options="current as current.clientId for current in clients">
+ <option value="">{{:: 'selectOne' | translate}}...</option>
+ </select>
+ </div>
+ <kc-tooltip>{{:: 'authz-policy-role-clients.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="clientRoles">{{:: 'client-roles' | translate}} <span class="required">*</span></label>
+
+ <div class="col-md-4">
+ <select ui-select2="{ minimumInputLength: 1}" id="clientRoles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
+ ng-options="role as role.name for role in clientRoles" data-ng-required="selectedRoles.length == 0" data-ng-disabled="!selectedClient">
+ </select>
+ </div>
+
+ <kc-tooltip>{{:: 'authz-policy-role-client-roles.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group clearfix" style="margin-top: -15px;">
+ <label class="col-md-2 control-label"></label>
+ <div class="col-sm-4" data-ng-show="hasClientRole()">
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th class="col-sm-5">{{:: 'name' | translate}}</th>
+ <th class="col-sm-5">{{:: 'client' | translate}}</th>
+ <th>{{:: 'authz-required' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
- <tr ng-repeat="role in selectedRoles | orderBy:'name'">
+ <tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="role.clientRole">
<td>{{role.name}}</td>
+ <td>{{role.container.name}}</td>
+ <td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
<td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedRoles, $index);">{{:: 'remove' | translate}}</button>
+ <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
</td>
</tr>
<tr data-ng-show="!selectedRoles.length">
@@ -98,7 +152,6 @@
</div>
<input type="hidden" data-ng-model="policy.type"/>
</fieldset>
-
<div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html
index ce62c33..d875e86 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html
@@ -12,6 +12,10 @@
<div data-ng-show="showResult">
<br>
<a href="" data-ng-click="showRequestTab()">{{:: 'authz-evaluation-new' | translate}}</a>
+ |
+ <a href="" data-ng-click="reevaluate()">{{:: 'authz-evaluation-re-evaluate' | translate}}</a>
+ |
+ <a href="" data-ng-click="showAuthzData()">{{:: 'authz-show-authorization-data' | translate}}</a>
</div>
<div data-ng-show="evaluationResult && !showResult">
@@ -19,6 +23,16 @@
<a href="" data-ng-click="showResultTab()">{{:: 'authz-evaluation-previous' | translate}}</a>
</div>
+ <div data-ng-show="showRpt">
+ <div class="form-group">
+ <label class="col-sm-1 control-label" for="rpt">{{:: 'authz-evaluation-authorization-data' | translate}}</label>
+ <div class="col-md-6">
+ <textarea id="rpt" class="form-control" rows="20">{{evaluationResult.rpt | json}}</textarea>
+ </div>
+ <kc-tooltip>{{:: 'authz-evaluation-authorization-data.tooltip' | translate}}</kc-tooltip>
+ </div>
+ </div>
+
<div data-ng-hide="showResult">
<form class="form-horizontal" name="clientForm" novalidate>
<fieldset>
@@ -56,12 +70,6 @@
<kc-tooltip>{{:: 'authz-evaluation-user.tooltip' | translate}}</kc-tooltip>
</div>
- <div class="form-group">
- <div class="col-md-10 col-md-offset-2">
- <button class="btn btn-primary" data-ng-click="entitlements()" data-ng-disabled="authzRequest.userId == null || authzRequest.clientId == null">{{:: 'authz-entitlements' | translate}}</button>
- </div>
- </div>
-
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="reqActions">{{:: 'roles' | translate}} <span class="required"
data-ng-show="!authzRequest.userId || authzRequest.userId == null">*</span></label>
@@ -268,7 +276,7 @@
</fieldset>
</form>
</div>
- <div data-ng-include="resultUrl" data-ng-show="showResult"/>
+ <div data-ng-include="resultUrl" data-ng-show="showResult && !showRpt"/>
</div>
<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
index 8a748f4..524d55d 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
@@ -43,14 +43,14 @@
<ul>
<li data-ng-repeat="policyResult in result.policies">
<strong><a
- href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
+ href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
decision was <span style="color: green" data-ng-show="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
<span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision.</a>
<ul>
<li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
<strong><a
- href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
+ href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
voted to <span style="color: green"
data-ng-show="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>
<span style="color: red" data-ng-hide="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>.</a>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
index 8d1b0e1..39cfc77 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
@@ -24,11 +24,33 @@
</tr>
<tr data-ng-hide="scopes.length == 0">
<th>{{:: 'name' | translate}}</th>
+ <th>{{:: 'authz-resources' | translate}}</th>
+ <th>{{:: 'authz-permissions' | translate}}</th>
+ <th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="scope in scopes | filter:search | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>
+ <td>
+ <span data-ng-show="!scope.resources.length">{{:: 'authz-no-resources-assigned' | translate}}</span>
+ <span data-ng-show="scope.resources.length > 0">
+ <span ng-repeat="resource in scope.resources">
+ <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource/{{resource._id}}">{{resource.name}}</a>{{$last ? '' : ', '}}
+ </span>
+ </span>
+ </td>
+ <td>
+ <span data-ng-show="!scope.policies.length">{{:: 'authz-no-permission-assigned' | translate}}</span>
+ <span data-ng-show="scope.policies.length > 0">
+ <span ng-repeat="policy in scope.policies">
+ <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a>{{$last ? '' : ', '}}
+ </span>
+ </span>
+ </td>
+ <td class="kc-action-cell" style="vertical-align: middle">
+ <button class="btn btn-default btn-block btn-sm" ng-click="createPolicy(scope);">{{:: 'authz-create-permission' | translate}}</button>
+ </td>
</tr>
<tr data-ng-show="(scopes | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>