keycloak-aplcache
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 15(+14 -1)
adapters/oidc/js/pom.xml 19(+19 -0)
authz/client/src/main/java/org/keycloak/authorization/client/util/HttpResponseException.java 3(+2 -1)
core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java 12(+12 -0)
examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java 6(+3 -3)
examples/authz/photoz/photoz-authz-policy/src/main/resources/com.photoz.authz.policy.admin/Main.drl 0(+0 -0)
examples/authz/photoz/photoz-authz-policy/src/main/resources/com.photoz.authz.policy.resource.owner/Main.drl 0(+0 -0)
examples/authz/photoz/photoz-authz-policy/src/main/resources/com.photoz.authz.policy.user/Main.drl 0(+0 -0)
examples/authz/photoz/photoz-authz-policy/src/main/resources/com/photoz/authz/policy/contextual/Main.drl 2(+1 -1)
examples/authz/photoz/photoz-html5-client/src/main/webapp/lib/angular/angular-resource.min.js 0(+0 -0)
examples/authz/photoz/photoz-realm.json 24(+12 -12)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/entity/Album.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/entity/Photo.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/ErrorResponse.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/PhotozApplication.java 0(+0 -0)
examples/authz/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml 0(+0 -0)
examples/authz/photoz/pom.xml 10(+5 -5)
examples/authz/photoz/README.md 47(+23 -24)
examples/authz/pom.xml 2(+1 -1)
examples/authz/servlet-authz/README.md 24(+14 -10)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 11(+8 -3)
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 add1c83..3ae286f 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
@@ -129,7 +129,7 @@ public abstract class AbstractPolicyEnforcer {
Set<String> allowedScopes = permission.getScopes();
if (permission.getResourceSetId() != null) {
- if (permission.getResourceSetId().equals(actualPathConfig.getId())) {
+ if (isResourcePermission(actualPathConfig, permission)) {
if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
@@ -211,6 +211,7 @@ public abstract class AbstractPolicyEnforcer {
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setInstance(true);
+ config.setParentConfig(originalConfig);
this.paths.add(config);
@@ -240,4 +241,16 @@ public abstract class AbstractPolicyEnforcer {
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
return new AuthorizationContext(accessToken, this.paths);
}
+
+ private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
+ // first we try a match using resource id
+ boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
+
+ // 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());
+ }
+
+ return resourceMatch;
+ }
}
adapters/oidc/js/pom.xml 19(+19 -0)
diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml
index 315f4b7..b03ccea 100755
--- a/adapters/oidc/js/pom.xml
+++ b/adapters/oidc/js/pom.xml
@@ -58,6 +58,25 @@
<goal>minify</goal>
</goals>
</execution>
+ <execution>
+ <id>min-authz-js</id>
+ <phase>compile</phase>
+ <configuration>
+ <charset>utf-8</charset>
+ <webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
+ <jsSourceDir>.</jsSourceDir>
+ <jsSourceFiles>
+ <jsSourceFile>keycloak-authz.js</jsSourceFile>
+ </jsSourceFiles>
+
+ <webappTargetDir>${project.build.directory}/classes</webappTargetDir>
+ <jsTargetDir>.</jsTargetDir>
+ <jsFinalFile>keycloak-authz.js</jsFinalFile>
+ </configuration>
+ <goals>
+ <goal>minify</goal>
+ </goals>
+ </execution>
</executions>
</plugin>
</plugins>
diff --git a/adapters/oidc/js/src/main/resources/keycloak-authz.js b/adapters/oidc/js/src/main/resources/keycloak-authz.js
new file mode 100644
index 0000000..7658352
--- /dev/null
+++ b/adapters/oidc/js/src/main/resources/keycloak-authz.js
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ *
+ */
+
+(function( window, undefined ) {
+
+ var KeycloakAuthorization = function (keycloak) {
+ var _instance = this;
+ this.rpt = null;
+
+ this.init = function () {
+ var request = new XMLHttpRequest();
+
+ request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma-configuration');
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ _instance.config = JSON.parse(request.responseText);
+ } else {
+ console.error('Could not obtain configuration from server.');
+ }
+ }
+ }
+
+ request.send(null);
+ };
+
+ /**
+ * This method enables client applications to better integrate with resource servers protected by a Keycloak
+ * policy enforcer.
+ *
+ * In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
+ * necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
+ * depending on how the policy enforcer at the resource server was configured.
+ */
+ this.authorize = function (wwwAuthenticateHeader) {
+ this.then = function (onGrant, onDeny, onError) {
+ if (wwwAuthenticateHeader.startsWith('UMA')) {
+ var params = wwwAuthenticateHeader.split(',');
+
+ for (i = 0; i < params.length; i++) {
+ var param = params[i].split('=');
+
+ if (param[0] == 'ticket') {
+ var request = new XMLHttpRequest();
+
+ request.open('POST', _instance.config.rpt_endpoint, true);
+ request.setRequestHeader('Content-Type', 'application/json')
+ request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
+
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ var status = request.status;
+
+ if (status >= 200 && status < 300) {
+ var rpt = JSON.parse(request.responseText).rpt;
+ _instance.rpt = rpt;
+ onGrant(rpt);
+ } else if (status == 403) {
+ if (onDeny) {
+ onDeny();
+ } else {
+ console.error('Authorization request was denied by the server.');
+ }
+ } else {
+ if (onError) {
+ onError();
+ } else {
+ console.error('Could not obtain authorization data from server.');
+ }
+ }
+ }
+ };
+
+ var ticket = param[1].substring(1, param[1].length - 1).trim();
+
+ request.send(JSON.stringify(
+ {
+ ticket: ticket,
+ rpt: _instance.rpt
+ }
+ ));
+ }
+ }
+ } else if (wwwAuthenticateHeader.startsWith('KC_ETT')) {
+ var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
+ var clientId = null;
+
+ for (i = 0; i < params.length; i++) {
+ var param = params[i].split('=');
+
+ if (param[0] == 'realm') {
+ clientId = param[1].substring(1, param[1].length - 1).trim();
+ }
+ }
+
+ _instance.entitlement(clientId).then(onGrant, onDeny, onError);
+ }
+ };
+
+ /**
+ * Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
+ */
+ this.entitlement = function (resourceSeververId) {
+ this.then = function (onGrant, onDeny, onError) {
+ var request = new XMLHttpRequest();
+
+ request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
+ request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
+
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ var status = request.status;
+
+ if (status >= 200 && status < 300) {
+ var rpt = JSON.parse(request.responseText).rpt;
+ _instance.rpt = rpt;
+ onGrant(rpt);
+ } else if (status == 403) {
+ if (onDeny) {
+ onDeny();
+ } else {
+ console.error('Authorization request was denied by the server.');
+ }
+ } else {
+ if (onError) {
+ onError();
+ } else {
+ console.error('Could not obtain authorization data from server.');
+ }
+ }
+ }
+ };
+
+ request.send(null);
+ };
+
+ return this;
+ };
+
+ return this;
+ };
+
+ this.init(this);
+ };
+
+ if ( typeof module === "object" && module && typeof module.exports === "object" ) {
+ module.exports = KeycloakAuthorization;
+ } else {
+ window.KeycloakAuthorization = KeycloakAuthorization;
+
+ if ( typeof define === "function" && define.amd ) {
+ define( "keycloak-authorization", [], function () { return KeycloakAuthorization; } );
+ }
+ }
+})( window );
\ No newline at end of file
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethod.java b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethod.java
index a693263..be83987 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethod.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethod.java
@@ -89,7 +89,7 @@ public class HttpMethod<R> {
int statusCode = statusLine.getStatusCode();
if (statusCode < 200 || statusCode >= 300) {
- throw new HttpResponseException(statusCode, statusLine.getReasonPhrase(), bytes);
+ throw new HttpResponseException("Unexpected response from server: " + statusCode + " / " + statusLine.getReasonPhrase(), statusCode, statusLine.getReasonPhrase(), bytes);
}
if (bytes == null) {
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpResponseException.java b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpResponseException.java
index 9b783e7..3531f40 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpResponseException.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpResponseException.java
@@ -26,7 +26,8 @@ public class HttpResponseException extends RuntimeException {
private final String reasonPhrase;
private final byte[] bytes;
- public HttpResponseException(int statusCode, String reasonPhrase, byte[] bytes) {
+ public HttpResponseException(String message, int statusCode, String reasonPhrase, byte[] bytes) {
+ super(message);
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
this.bytes = bytes;
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 98a4045..5145ec7 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
@@ -17,6 +17,7 @@
*/
package org.keycloak.representations.adapters.config;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
@@ -99,6 +100,9 @@ public class PolicyEnforcerConfig {
private String id;
private boolean instance;
+ @JsonIgnore
+ private PathConfig parentConfig;
+
public String getPath() {
return this.path;
}
@@ -169,6 +173,14 @@ public class PolicyEnforcerConfig {
public void setInstance(boolean instance) {
this.instance = instance;
}
+
+ public void setParentConfig(PathConfig parentConfig) {
+ this.parentConfig = parentConfig;
+ }
+
+ public PathConfig getParentConfig() {
+ return parentConfig;
+ }
}
public static class MethodConfig {
diff --git a/examples/authz/hello-world/hello-world-authz-realm.json b/examples/authz/hello-world/hello-world-authz-realm.json
index a263c69..3ab917c 100644
--- a/examples/authz/hello-world/hello-world-authz-realm.json
+++ b/examples/authz/hello-world/hello-world-authz-realm.json
@@ -12,16 +12,18 @@
"enabled" : true,
"credentials" : [ {
"type" : "password",
- "value" : "password"
- } ]
+ "value" : "alice"
+ } ],
+ "realmRoles" : ["uma_authorization"]
},
{
"username" : "jdoe",
"enabled" : true,
"credentials" : [ {
"type" : "password",
- "value" : "password"
- } ]
+ "value" : "jdoe"
+ } ],
+ "realmRoles" : ["uma_authorization"]
},
{
"username" : "service-account-hello-world-authz-service",
@@ -38,7 +40,9 @@
"secret" : "secret",
"authorizationServicesEnabled" : true,
"enabled" : true,
- "redirectUris" : [ "http://localhost:8080/hello-world-authz-service" ],
+ "redirectUris" : [ "http://localhost:8080/hello-world-authz-service/*" ],
+ "baseUrl": "http://localhost:8080/hello-world-authz-service",
+ "adminUrl": "http://localhost:8080/hello-world-authz-service",
"directAccessGrantsEnabled" : true
}
]
diff --git a/examples/authz/hello-world/hello-world-authz-service.json b/examples/authz/hello-world/hello-world-authz-service.json
index 24bd27f..ea56e62 100644
--- a/examples/authz/hello-world/hello-world-authz-service.json
+++ b/examples/authz/hello-world/hello-world-authz-service.json
@@ -1,24 +1,29 @@
{
"resources": [
{
- "name": "Hello World Resource"
+ "name": "Default Resource",
+ "uri": "/*",
+ "type": "urn:hello-world-authz-service:resources:default"
}
],
"policies": [
{
- "name": "Only Special Users Policy",
- "type": "user",
- "logic": "POSITIVE",
+ "name": "Only From Realm Policy",
+ "description": "A policy that grants access only for users within this realm",
+ "type": "js",
"config": {
- "users": "[\"alice\"]"
+ "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}"
}
},
{
- "name": "Hello World Resource Permission",
+ "name": "Default Permission",
+ "description": "A permission that applies to the default resource type",
"type": "resource",
"config": {
- "resources": "[\"Hello World Resource\"]",
- "applyPolicies": "[\"Only Special Users Policy\"]"
+ "defaultResourceType": "urn:hello-world-authz-service:resources:default",
+ "default": "true",
+ "applyPolicies": "[\"Only From Realm Policy\"]"
}
}
]
diff --git a/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java b/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java
index 75ee0d3..2ab8788 100644
--- a/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java
+++ b/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java
@@ -49,7 +49,7 @@ public class AuthorizationClientExample {
// query the server for a resource with a given name
Set<String> resourceId = authzClient.protection()
.resource()
- .findByFilter("name=Hello World Resource");
+ .findByFilter("name=Default Resource");
// obtian a Entitlement API Token in order to get access to the Entitlement API.
// this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement
@@ -119,7 +119,7 @@ public class AuthorizationClientExample {
EntitlementRequest request = new EntitlementRequest();
PermissionRequest permission = new PermissionRequest();
- permission.setResourceSetName("Hello World Resource");
+ permission.setResourceSetName("Default Resource");
request.addPermission(permission);
@@ -157,6 +157,6 @@ public class AuthorizationClientExample {
* @return a string representing a EAT
*/
private static String getEntitlementAPIToken(AuthzClient authzClient) {
- return authzClient.obtainAccessToken("alice", "password").getToken();
+ return authzClient.obtainAccessToken("alice", "alice").getToken();
}
}
diff --git a/examples/authz/hello-world-authz-service/hello-world-authz-realm.json b/examples/authz/hello-world-authz-service/hello-world-authz-realm.json
new file mode 100644
index 0000000..3ab917c
--- /dev/null
+++ b/examples/authz/hello-world-authz-service/hello-world-authz-realm.json
@@ -0,0 +1,49 @@
+{
+ "realm" : "hello-world-authz",
+ "enabled" : true,
+ "privateKey" : "MIIEpQIBAAKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABAoIBAAwa4wVnKBOIS6srmYPfBTDNsTBBCEjxiYEErmn7JhoWxQ1DCPUxyxU6F177/q9Idqoj1FFOCtEO9P6/9+ym470HQmEQkR2Xxd1d3HOZy9oKuCro3ZbTDkVxY0JnlyxZz4MihGFxDH2e4MArfHy0sAgYbdIU+x2pWKGWSMzDd/TMSOExhc/sIQAg6ljbPCLLXCPQFAncoHRyGPrkRZs6UTZi5SJuCglVa2/3G+0drDdPuA83/mwsZfIBqQgbGbFgtq5T5C6CKMkPOQ42Rcclm7kEr6riTkJRo23EO1iOJVpxzI0tbxZsJAsW7zeqv0wWRyUgVfQAje6OdsNexp5aCtECgYEA6nMHCQ9xXvufCyzpIbYGxdAGqH6m1AR5gXerHqRiGNx+8UUt/E9cy/HTOhmZDK/eC4BT9tImeF01l1oSU/+wGKfux0SeAQchBhhq8GD6jmrtgczKAfZHp0Zrht7o9qu9KE7ZNWRmY1foJN9yNYmzY6qqHEy+zNo9amcqT7UZKO8CgYEA35sp9fMpMqkJE+NEJ9Ph/t2081BEkC0DYIuETZRSi+Ek5AliWTyEkg+oisTbWzi6fMQHS7W+M1SQP6djksLQNPP+353DKgup5gtKS+K/y2xNd7fSsNmkjW1bdJJpID7WzwwmwdahHxpcnFFuEXi5FkG3Vqmtd3cD0TYL33JlRy0CgYEA0+a3eybsDy9Zpp4m8IM3R98nxW8DlimdMLlafs2QpGvWiHdAgwWwF90wTxkHzgG+raKFQVbb0npcj7mnSyiUnxRZqt2H+eHZpUq4jR76F3LpzCGui2tvg+8QDMy4vwqmYyIxDCL8r9mqRnl3HpChBPoh2oY7BahTTjKEeZpzbR0CgYEAoNnVjX+mGzNNvGi4Fo5s/BIwoPcU20IGM+Uo/0W7O7Rx/Thi7x6BnzB0ZZ7GzRA51paNSQEsGXCzc5bOIjzR2cXLisDKK+zIAxwMDhrHLWZzM7OgdGeb38DTEUBhLzkE/VwYZUgoD1+/TxOkwhy9yCzt3gGhL1cF//GJCOwZvuECgYEAgsO4rdYScgCpsyePnHsFk+YtqtdORnmttF3JFcL3w2QneXuRwg2uW2Kfz8CVphrR9eOU0tiw38w6QTHIVeyRY8qqlHtiXj6dEYz7frh/k4hI29HwFx43rRpnAnN8kBEJYBYdbjaQ35Wsqkfu1tvHJ+6fxSwvQu/TVdGp0OfilAY=",
+ "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQAB",
+ "certificate" : "MIICsTCCAZkCBgFVETX4AzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFIZWxsbyBXb3JsZCBBdXRoWjAeFw0xNjA2MDIxMzAxMzdaFw0yNjA2MDIxMzAzMTdaMBwxGjAYBgNVBAMMEUhlbGxvIFdvcmxkIEF1dGhaMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQANm5gIT/c50lwjawM686gNXpppLA928WsCOn9NIIWjSKekP8Bf9S73kf7vWcsEppm5B8rRyRxolXmzwghv74L7uVDg8Injjgj+XbPVQP+cJqWpSaMZHF7UfWe0/4M945Xcbmsl5q+m9PmrPG0AaaZhqXHcp4ehB1H+awyRqiERpJUuwZNycw2+2kjDADpsFf8hZVUd1F6ReYyOkqUyUjbL+jYTC7ZBNa7Ok+w6HCXWgkgVATAgQXJRM3w14IOc5MH/vfMCrCl/eNQLbjGl9y7u8PKwh3MXHDO2OLqtg6hOTSrOGUPJZGmGtUAl+2/R7FzoWkML/BNe2hjsL6UJwg91",
+ "requiredCredentials" : [ "password" ],
+ "users" :
+ [
+ {
+ "username" : "alice",
+ "enabled" : true,
+ "credentials" : [ {
+ "type" : "password",
+ "value" : "alice"
+ } ],
+ "realmRoles" : ["uma_authorization"]
+ },
+ {
+ "username" : "jdoe",
+ "enabled" : true,
+ "credentials" : [ {
+ "type" : "password",
+ "value" : "jdoe"
+ } ],
+ "realmRoles" : ["uma_authorization"]
+ },
+ {
+ "username" : "service-account-hello-world-authz-service",
+ "enabled" : true,
+ "serviceAccountClientId" : "hello-world-authz-service",
+ "clientRoles": {
+ "hello-world-authz-service" : ["uma_protection"]
+ }
+ }
+ ],
+ "clients" : [
+ {
+ "clientId" : "hello-world-authz-service",
+ "secret" : "secret",
+ "authorizationServicesEnabled" : true,
+ "enabled" : true,
+ "redirectUris" : [ "http://localhost:8080/hello-world-authz-service/*" ],
+ "baseUrl": "http://localhost:8080/hello-world-authz-service",
+ "adminUrl": "http://localhost:8080/hello-world-authz-service",
+ "directAccessGrantsEnabled" : true
+ }
+ ]
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ea56e62
--- /dev/null
+++ b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
@@ -0,0 +1,30 @@
+{
+ "resources": [
+ {
+ "name": "Default Resource",
+ "uri": "/*",
+ "type": "urn:hello-world-authz-service:resources:default"
+ }
+ ],
+ "policies": [
+ {
+ "name": "Only From Realm Policy",
+ "description": "A policy that grants access only for users within this realm",
+ "type": "js",
+ "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}"
+ }
+ },
+ {
+ "name": "Default Permission",
+ "description": "A permission that applies to the default resource type",
+ "type": "resource",
+ "config": {
+ "defaultResourceType": "urn:hello-world-authz-service:resources:default",
+ "default": "true",
+ "applyPolicies": "[\"Only From Realm Policy\"]"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/authz/hello-world-authz-service/README.md b/examples/authz/hello-world-authz-service/README.md
new file mode 100644
index 0000000..a0cc40f
--- /dev/null
+++ b/examples/authz/hello-world-authz-service/README.md
@@ -0,0 +1,47 @@
+# About the Example Application
+
+This is a simple application to get you started with Keycloak Authorization Services.
+
+It provides a single page application which is protected by a policy enforcer that decides whether an user can access
+that page or not based on the permissions obtained from a Keycloak Server.
+
+## 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/hello-world-authz-service/hello-world-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 ``hello-world-authz``.
+
+Now, let's import another configuration using the Administration Console in order to configure the client application ``hello-world-authz-service`` as a resource server with all resources, scopes, permissions and policies.
+
+Click on ``Clients`` on the left side menu. Click on the ``hello-world-authz-service`` 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/hello-world-authz-service/hello-world-authz-service.json
+
+Now click ``Upload`` and the resource server will be updated accordingly.
+
+## Deploy and Run the Example Application
+
+To deploy the example application, follow these steps:
+
+ cd examples/authz/hello-world-authz-service
+ mvn clean package wildfly:deploy
+
+Now, try to access the client application using the following URL:
+
+ http://localhost:8080/hello-world-authz-service
+
+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
+
diff --git a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
index 04c0486..f303fe1 100644
--- a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,11 +1,11 @@
{
"realm": "hello-world-authz",
- "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmm2Nso+rUOYUYc4hO67LSf4s0pAKcqUbWWycS3fcz6Q4jg/SsBbIBJJXOMVR9GqwyTCVTH5s8Rb0+0pA+UrbZfMG2XIDnJoaGfJj9DvJwQkD+vzTvaS5q0ilP0tPlbusI5pyMi9xx+cjJBOvKR2GxjhcKrgb21lpmGcA1F1CPO3y/DT8GzTKg+9/nPKt1dKEUD7P5Uy5N7d8zz1fuOSLb5G267T1fKJvi6am8kCgM+agFVQ23j7w/aJ7T1EHUCZdaJ+aSODSYl8dM4RFNTjda0KMHHXqMMvd2+g8lZ0lAfstHywqZtCcHc9ULClVvQmQyXovn2qTktHAcD6BHTAgQIDAQAB",
+ "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "hello-world-authz-service",
"credentials": {
- "secret": "a7672d93-ea27-44a3-baa6-ba3536609067"
+ "secret": "secret"
},
"policy-enforcer": {
"on-deny-redirect-to" : "/hello-world-authz-service/error.jsp"
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/identity.js b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/identity.js
new file mode 100644
index 0000000..9a018e4
--- /dev/null
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/identity.js
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ *
+ */
+
+/**
+ * Creates an Identity object holding the information obtained from the access token issued by Keycloak, after a successful authentication,
+ * and a few utility methods to manage it.
+ */
+(function (window, undefined) {
+ var Identity = function (keycloak) {
+ this.loggedIn = true;
+
+ this.claims = {};
+ this.claims.name = keycloak.idTokenParsed.name;
+
+ this.authc = {};
+ this.authc.token = keycloak.token;
+
+ this.logout = function () {
+ keycloak.logout();
+ };
+
+ this.hasRole = function (name) {
+ if (keycloak && keycloak.hasRealmRole(name)) {
+ return true;
+ }
+ return false;
+ };
+
+ this.isAdmin = function () {
+ return this.hasRole("admin");
+ };
+
+ this.authorization = new KeycloakAuthorization(keycloak);
+ }
+
+ if ( typeof module === "object" && module && typeof module.exports === "object" ) {
+ module.exports = Identity;
+ } else {
+ window.Identity = Identity;
+
+ if ( typeof define === "function" && define.amd ) {
+ define( "identity", [], function () { return Identity; } );
+ }
+ }
+})( window );
\ No newline at end of file
examples/authz/pom.xml 2(+1 -1)
diff --git a/examples/authz/pom.xml b/examples/authz/pom.xml
index 318d01e..e9eb4eb 100755
--- a/examples/authz/pom.xml
+++ b/examples/authz/pom.xml
@@ -22,7 +22,7 @@
</properties>
<modules>
- <module>photoz-uma</module>
+ <module>photoz</module>
<module>servlet-authz</module>
<module>hello-world</module>
<module>hello-world-authz-service</module>
examples/authz/servlet-authz/README.md 24(+14 -10)
diff --git a/examples/authz/servlet-authz/README.md b/examples/authz/servlet-authz/README.md
index df52870..f93acb5 100644
--- a/examples/authz/servlet-authz/README.md
+++ b/examples/authz/servlet-authz/README.md
@@ -14,7 +14,7 @@ This application will also show you how to create a dynamic menu with the permis
## Create the Example Realm and a Resource Server
-Considering that your AuthZ Server is up and running, log in to the Keycloak Administration Console.
+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:
@@ -25,26 +25,30 @@ 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 ``servlet-authz-app`` client application as a resource server with all resources, scopes, permissions and policies.
+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 ``Authorization`` on the left side menu. Click on the ``Create`` button on the top of the resource server table. This will
-open the page that allows you to create a new resource server.
+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 a new resource server will be created based on the ``servlet-authz-app`` client application.
+Now click ``Upload`` and the resource server will be updated accordingly.
## Deploy and Run the Example Applications
-To deploy the example applications, follow these steps:
+To deploy the example application, follow these steps:
cd examples/authz/servlet-authz
- mvn wildfly:deploy
+ 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 (premium user)
-* username: alice / password: alice (regular user)
-* username: admin / password: admin (administrator)
\ No newline at end of file
+* username: jdoe / password: jdoe
+* username: alice / password: alice
+* username: admin / password: admin
\ No newline at end of file
diff --git a/examples/authz/servlet-authz/src/main/webapp/accessDenied.jsp b/examples/authz/servlet-authz/src/main/webapp/accessDenied.jsp
index be85c22..6f25023 100644
--- a/examples/authz/servlet-authz/src/main/webapp/accessDenied.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/accessDenied.jsp
@@ -1,8 +1,6 @@
-<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
-<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
<html>
<body>
- <h2 style="color: red">You can not access this resource. Click <a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
- .queryParam("redirect_uri", "/servlet-authz-app").build("servlet-authz").toString()%>">here</a> to log in as a different user.</h2>
+ <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/examples/authz/servlet-authz/src/main/webapp/index.jsp b/examples/authz/servlet-authz/src/main/webapp/index.jsp
index 118f142..78c5444 100755
--- a/examples/authz/servlet-authz/src/main/webapp/index.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/index.jsp
@@ -1,6 +1,4 @@
<%@page import="org.keycloak.AuthorizationContext" %>
-<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
-<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
<%@ page import="org.keycloak.representations.authorization.Permission" %>
@@ -11,8 +9,7 @@
<html>
<body>
- <h2>Click <a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
- .queryParam("redirect_uri", "/servlet-authz-app").build("servlet-authz").toString()%>">here</a> to logout.</h2>
+ <%@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>
diff --git a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
new file mode 100644
index 0000000..95365ea
--- /dev/null
+++ b/examples/authz/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 <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
diff --git a/examples/authz/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp b/examples/authz/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
index 554b250..5946cd6 100644
--- a/examples/authz/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
@@ -1,8 +1,6 @@
-<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
-<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
<html>
<body>
- <h2>Only Administrators can access this page. Click <a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
- .queryParam("redirect_uri", "/servlet-authz-app").build("servlet-authz").toString()%>">here</a> to logout.</h2></h2>
+ <h2>Only Administrators can access this page.</h2>
+ <%@include file="../../logout-include.jsp"%>
</body>
</html>
\ No newline at end of file
diff --git a/examples/authz/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp b/examples/authz/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
index 7240a98..1473d22 100644
--- a/examples/authz/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
@@ -1,6 +1,4 @@
<%@page import="org.keycloak.AuthorizationContext" %>
-<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
-<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
<%
@@ -10,8 +8,8 @@
<html>
<body>
-<h2>Any authenticated user can access this page. Click <a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
- .queryParam("redirect_uri", "/servlet-authz-app").build("servlet-authz").toString()%>">here</a> to logout.</h2>
+<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>
diff --git a/examples/authz/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp b/examples/authz/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
index f172573..9244f9c 100644
--- a/examples/authz/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
@@ -1,9 +1,6 @@
-<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
-<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
<html>
<body>
-<h2>Only for premium users. Click <a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
- .queryParam("redirect_uri", "/servlet-authz-app").build("servlet-authz").toString()%>">here</a> to logout.</h2>
-
+<h2>Only for premium users.</h2>
+<%@include file="../../logout-include.jsp"%>
</body>
</html>
\ No newline at end of file
diff --git a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
index 7f37597..eaffea8 100644
--- a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
@@ -8,5 +8,7 @@
"credentials": {
"secret": "secret"
},
- "policy-enforcer": {}
+ "policy-enforcer": {
+ "on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp"
+ }
}
\ No newline at end of file
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 1c31fcc..84e5295 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -463,7 +463,7 @@ public class ResourceServerService {
"\n" +
"// using attributes from the evaluation context to obtain the realm\n" +
"var contextAttributes = context.getAttributes();\n" +
- "var realmName = contextAttributes.getValue('kc.authz.context.authc.realm').asString(0);\n" +
+ "var realmName = contextAttributes.getValue('kc.realm.name').asString(0);\n" +
"\n" +
"// using attributes from the identity to obtain the issuer\n" +
"var identity = context.getIdentity();\n" +
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 eaa8c78..ad154a6 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -106,7 +106,10 @@ public class AuthorizationTokenService {
List<Permission> entitlements = Permissions.allPermits(results);
if (entitlements.isEmpty()) {
- asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN));
+ asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
+ .entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
+ .allowedOrigins(identity.getAccessToken())
+ .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
} else {
AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken())
@@ -217,12 +220,14 @@ public class AuthorizationTokenService {
}
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
- if (!Tokens.verifySignature(request.getTicket(), getRealm().getPublicKey())) {
+ String ticketString = request.getTicket();
+
+ if (ticketString == null || !Tokens.verifySignature(ticketString, getRealm().getPublicKey())) {
throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
}
try {
- PermissionTicket ticket = new JWSInput(request.getTicket()).readJsonContent(PermissionTicket.class);
+ PermissionTicket ticket = new JWSInput(ticketString).readJsonContent(PermissionTicket.class);
if (!ticket.isActive()) {
throw new ErrorResponseException("invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
index bc967b9..fc929ec 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
@@ -57,23 +57,23 @@ public class KeycloakEvaluationContext implements EvaluationContext {
public Attributes getAttributes() {
HashMap<String, Collection<String>> attributes = new HashMap<>();
- attributes.put("kc.authz.context.time.date_time", Arrays.asList(new SimpleDateFormat("MM/dd/yyyy hh:mm:ss").format(new Date())));
- attributes.put("kc.authz.context.client.network.ip_address", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteAddr()));
- attributes.put("kc.authz.context.client.network.host", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteHost()));
+ attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("MM/dd/yyyy hh:mm:ss").format(new Date())));
+ attributes.put("kc.client.network.ip_address", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteAddr()));
+ attributes.put("kc.client.network.host", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteHost()));
AccessToken accessToken = this.identity.getAccessToken();
if (accessToken != null) {
- attributes.put("kc.authz.context.client_id", Arrays.asList(accessToken.getIssuedFor()));
+ attributes.put("kc.client.id", Arrays.asList(accessToken.getIssuedFor()));
}
List<String> userAgents = this.keycloakSession.getContext().getRequestHeaders().getRequestHeader("User-Agent");
if (userAgents != null) {
- attributes.put("kc.authz.context.client.user_agent", userAgents);
+ attributes.put("kc.client.user_agent", userAgents);
}
- attributes.put("kc.authz.context.authc.realm", Arrays.asList(this.keycloakSession.getContext().getRealm().getName()));
+ attributes.put("kc.realm.name", Arrays.asList(this.keycloakSession.getContext().getRealm().getName()));
return Attributes.from(attributes);
}
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 983646b..df6f54d 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -80,8 +80,9 @@ public class EntitlementService {
this.authorization = authorization;
}
+ @Path("{resource_server_id}")
@OPTIONS
- public Response authorizePreFlight() {
+ public Response authorizePreFlight(@PathParam("resource_server_id") String resourceServerId) {
return Cors.add(this.request, Response.ok()).auth().preflight().build();
}
@@ -118,7 +119,10 @@ public class EntitlementService {
List<Permission> entitlements = Permissions.allPermits(results);
if (entitlements.isEmpty()) {
- asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN));
+ asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
+ .entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
+ .allowedOrigins(identity.getAccessToken())
+ .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
} else {
asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
}
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 240fafa..43204b8 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -26,6 +26,7 @@ import org.keycloak.authorization.model.ResourceServer;
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.StoreFactory;
import org.keycloak.representations.authorization.Permission;
@@ -58,9 +59,10 @@ public final class Permissions {
public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization) {
List<ResourcePermission> permissions = new ArrayList<>();
StoreFactory storeFactory = authorization.getStoreFactory();
+ ResourceStore resourceStore = storeFactory.getResourceStore();
- storeFactory.getResourceStore().findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
- storeFactory.getResourceStore().findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
+ resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
+ resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
return permissions;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/JsResource.java b/services/src/main/java/org/keycloak/services/resources/JsResource.java
index 32161a4..c74abf0 100755
--- a/services/src/main/java/org/keycloak/services/resources/JsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/JsResource.java
@@ -44,55 +44,90 @@ public class JsResource {
@GET
@Path("/keycloak.js")
@Produces("text/javascript")
- public Response getJs() {
- InputStream inputStream = getClass().getClassLoader().getResourceAsStream("keycloak.js");
- if (inputStream != null) {
- CacheControl cacheControl = new CacheControl();
- cacheControl.setNoTransform(false);
- cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
+ public Response getKeycloakJs() {
+ return getJs("keycloak.js");
+ }
- return Response.ok(inputStream).type("text/javascript").cacheControl(cacheControl).build();
- } else {
+ @GET
+ @Path("/{version}/keycloak.js")
+ @Produces("text/javascript")
+ public Response getKeycloakJsWithVersion(@PathParam("version") String version) {
+ if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
+
+ return getKeycloakJs();
}
@GET
- @Path("/{version}/keycloak.js")
+ @Path("/keycloak.min.js")
+ @Produces("text/javascript")
+ public Response getKeycloakMinJs() {
+ return getJs("keycloak.min.js");
+ }
+
+ @GET
+ @Path("/{version}/keycloak.min.js")
@Produces("text/javascript")
- public Response getJsWithVersion(@PathParam("version") String version) {
+ public Response getKeycloakMinJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
- return getJs();
+ return getKeycloakMinJs();
}
+ /**
+ * Get keycloak-authz.js file for javascript clients
+ *
+ * @return
+ */
@GET
- @Path("/keycloak.min.js")
+ @Path("/keycloak-authz.js")
@Produces("text/javascript")
- public Response getMinJs() {
- InputStream inputStream = getClass().getClassLoader().getResourceAsStream("keycloak.min.js");
- if (inputStream != null) {
- CacheControl cacheControl = new CacheControl();
- cacheControl.setNoTransform(false);
- cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
+ public Response getKeycloakAuthzJs() {
+ return getJs("keycloak-authz.js");
+ }
- return Response.ok(inputStream).type("text/javascript").cacheControl(cacheControl).build();
- } else {
+ @GET
+ @Path("/{version}/keycloak-authz.js")
+ @Produces("text/javascript")
+ public Response getKeycloakAuthzJsWithVersion(@PathParam("version") String version) {
+ if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
+
+ return getKeycloakAuthzJs();
}
@GET
- @Path("/{version}/keycloak.min.js")
+ @Path("/keycloak-authz.min.js")
@Produces("text/javascript")
- public Response getMinJsWithVersion(@PathParam("version") String version) {
+ public Response getKeycloakAuthzMinJs() {
+ return getJs("keycloak-authz.min.js");
+ }
+
+ @GET
+ @Path("/{version}/keycloak-authz.min.js")
+ @Produces("text/javascript")
+ public Response getKeycloakAuthzMinJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
- return getMinJs();
+ return getKeycloakAuthzMinJs();
}
+ private Response getJs(String name) {
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name);
+ if (inputStream != null) {
+ CacheControl cacheControl = new CacheControl();
+ cacheControl.setNoTransform(false);
+ cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
+
+ return Response.ok(inputStream).type("text/javascript").cacheControl(cacheControl).build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ }
}
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 8876f30..0786eab 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
@@ -261,7 +261,7 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest
config.put("code",
"var contextAttributes = $evaluation.getContext().getAttributes();" +
- "var networkAddress = contextAttributes.getValue('kc.authz.context.client.network.ip_address');" +
+ "var networkAddress = contextAttributes.getValue('kc.client.network.ip_address');" +
"if ('127.0.0.1'.equals(networkAddress.asInetAddress(0).getHostAddress())) {" +
"$evaluation.grant();" +
"}");
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 e07ed1a..ecbe9d5 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
@@ -1044,7 +1044,7 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
custom: true
},
{
- key : "kc.authz.context.authc.method",
+ key : "kc.identity.authc.method",
name : "Authentication Method",
values: [
{
@@ -1062,23 +1062,23 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
]
},
{
- key : "kc.authz.context.authc.realm",
+ key : "kc.realm.name",
name : "Realm"
},
{
- key : "kc.authz.context.time.date_time",
+ key : "kc.time.date_time",
name : "Date/Time (MM/dd/yyyy hh:mm:ss)"
},
{
- key : "kc.authz.context.client.network.ip_address",
+ key : "kc.client.network.ip_address",
name : "Client IPv4 Address"
},
{
- key : "kc.authz.context.client.network.host",
+ key : "kc.client.network.host",
name : "Client Host"
},
{
- key : "kc.authz.context.client.user_agent",
+ key : "kc.client.user_agent",
name : "Client/User Agent"
}
];
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html b/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html
index 4919fe6..d03eefb 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html
@@ -5,7 +5,7 @@
<ul class="nav nav-tabs nav-tabs-pf" data-ng-hide="create && !path[4]" style="margin-left: 15px">
<li ng-class="{active: !path[6]}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/">Settings</a></li>
<li ng-class="{active: path[6] == 'resource'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource">Resources</a></li>
- <li ng-class="{active: path[6] == 'scope'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope">Scopes</a></li>
+ <li ng-class="{active: path[6] == 'scope'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope">Authorization Scopes</a></li>
<li ng-class="{active: path[6] == 'policy'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy">Policies</a></li>
<li ng-class="{active: path[6] == 'permission'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission">Permissions</a></li>
<li ng-class="{active: path[6] == 'evaluate'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/evaluate">Evaluate</a></li>