keycloak-aplcache
Changes
examples/authz/photoz/photoz-realm.json 19(+17 -2)
Details
examples/authz/photoz/photoz-realm.json 19(+17 -2)
diff --git a/examples/authz/photoz/photoz-realm.json b/examples/authz/photoz/photoz-realm.json
index baa8f66..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",
@@ -58,6 +68,9 @@
"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-authz-service.json b/examples/authz/photoz/photoz-restful-api-authz-service.json
index 6c786e7..6547d2f 100644
--- a/examples/authz/photoz/photoz-restful-api-authz-service.json
+++ b/examples/authz/photoz/photoz-restful-api-authz-service.json
@@ -70,13 +70,13 @@
},
{
"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": {
"applyPolicies": "[]",
- "roles": "[{\"id\":\"user\"}]"
+ "roles": "[{\"id\":\"user\"},{\"id\":\"manage-albums\",\"required\":true}]"
}
},
{
@@ -97,7 +97,7 @@
"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\"]"
}
},
{
@@ -107,7 +107,7 @@
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
- "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
+ "applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
}
},
{
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 93c5ce8..567675f 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -45,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;
@@ -61,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;
/**
@@ -256,7 +259,35 @@ public class ResourceServerService {
try {
List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> {
- roleConfig.put("id", realm.getRole(roleConfig.get("id").toString()).getId());
+ 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);
+ }
+
+ RoleModel role;
+
+ if (clientId == null) {
+ role = realm.getRole(roleName);
+ } else {
+ role = realm.getClientByClientId(clientId).getRole(roleName);
+ }
+
+ // fallback to find any client role with the given name
+ if (role == null) {
+ String finalRoleName = roleName;
+ role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null)
+ .findFirst().orElse(null);
+ }
+
+ if (role == null) {
+ throw new RuntimeException("Error while importing configuration. Role [" + role + "] could not be found.");
+ }
+
+ roleConfig.put("id", role.getId());
return roleConfig;
}).collect(Collectors.toList())));
} catch (Exception e) {
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
index baa8f66..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",
@@ -58,6 +68,9 @@
"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-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
index 6c786e7..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
@@ -70,13 +70,13 @@
},
{
"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": {
"applyPolicies": "[]",
- "roles": "[{\"id\":\"user\"}]"
+ "roles": "[{\"id\":\"user\"},{\"id\":\"manage-albums\",\"required\":true}]"
}
},
{
@@ -97,7 +97,7 @@
"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\"]"
}
},
{
@@ -107,7 +107,7 @@
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
- "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
+ "applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
}
},
{
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 0c3b108..4721737 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,6 +46,9 @@ 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();
Form.setInputValue(this.driver.findElement(By.id("album.name")), name);
@@ -88,13 +93,53 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
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) {
+ 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);
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 59a7a31..8a8d483 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
@@ -23,20 +23,30 @@ 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.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.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;
@@ -84,7 +94,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
importResourceServerSettings();
}
- @Test
public void testCreateDeleteAlbum() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
@@ -106,7 +115,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
- @Test
public void testOnlyOwnerCanDeleteAlbum() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
@@ -152,7 +160,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
- @Test
public void testRegularUserCanNotAccessAdminResources() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
@@ -165,7 +172,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
- @Test
public void testAdminOnlyFromSpecificAddress() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
@@ -211,6 +217,23 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
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();
@@ -241,7 +264,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
- @Test
public void testAdminWithoutPermissionsToDeleteScopePermission() throws Exception {
try {
this.deployer.deploy(RESOURCE_SERVER_ID);
@@ -305,13 +327,115 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
+ 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);
+ }
+ }
+
+ 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(new Consumer<Map>() {
+ @Override
+ public void accept(Map 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);
+ }
+ }
+
private void importResourceServerSettings() throws FileNotFoundException {
getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-restful-api-authz-service.json")), ResourceServerRepresentation.class));
}
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