keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 5(+3 -2)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java 9(+8 -1)
authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java 45(+45 -0)
authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java 2(+1 -1)
authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java 15(+7 -8)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java 16(+5 -11)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 31(+10 -21)
services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java 6(+3 -3)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 30(+11 -19)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/keystore.jks 0(+0 -0)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json 9(+8 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java 209(+209 -0)
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 1ba9cc5..61f46f1 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
@@ -29,6 +29,7 @@ import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
@@ -203,7 +204,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
- return new AuthorizationContext() {
+ return new ClientAuthorizationContext(authzClient) {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
@@ -252,7 +253,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
- return new AuthorizationContext(accessToken, this.paths);
+ return new ClientAuthorizationContext(accessToken, this.paths, authzClient);
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
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 679a33c..7f21eef 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
@@ -21,7 +21,9 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.RegistrationResponse;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
@@ -56,7 +58,12 @@ public class PolicyEnforcer {
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment;
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
- this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
+ this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() {
+ @Override
+ public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
+ ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
+ }
+ });
this.pathMatcher = new PathMatcher(this.authzClient);
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java b/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java
index 0221af6..5ed1306 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java
@@ -53,13 +53,17 @@ public class AuthzClient {
}
public static AuthzClient create(Configuration configuration) {
- return new AuthzClient(configuration);
+ return new AuthzClient(configuration, configuration.getClientAuthenticator());
+ }
+
+ public static AuthzClient create(Configuration configuration, ClientAuthenticator authenticator) {
+ return new AuthzClient(configuration, authenticator);
}
private final ServerConfiguration serverConfiguration;
private final Configuration deployment;
- private AuthzClient(Configuration configuration) {
+ private AuthzClient(Configuration configuration, ClientAuthenticator authenticator) {
if (configuration == null) {
throw new IllegalArgumentException("Client configuration can not be null.");
}
@@ -72,7 +76,9 @@ public class AuthzClient {
configurationUrl += "/realms/" + configuration.getRealm() + "/.well-known/uma-configuration";
- this.http = new Http(configuration);
+ this.deployment = configuration;
+
+ this.http = new Http(configuration, authenticator != null ? authenticator : configuration.getClientAuthenticator());
try {
this.serverConfiguration = this.http.<ServerConfiguration>get(URI.create(configurationUrl))
@@ -83,8 +89,10 @@ public class AuthzClient {
}
this.http.setServerConfiguration(this.serverConfiguration);
+ }
- this.deployment = configuration;
+ private AuthzClient(Configuration configuration) {
+ this(configuration, null);
}
public ProtectionResource protection() {
@@ -106,7 +114,7 @@ public class AuthzClient {
public AccessTokenResponse obtainAccessToken() {
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
.authentication()
- .oauth2ClientCredentials()
+ .client()
.response()
.json(AccessTokenResponse.class)
.execute();
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java b/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
new file mode 100644
index 0000000..73bcd9f
--- /dev/null
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
@@ -0,0 +1,45 @@
+/*
+ * 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.authorization.client;
+
+import java.util.Map;
+
+import org.keycloak.AuthorizationContext;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
+
+/**
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClientAuthorizationContext extends AuthorizationContext {
+
+ private final AuthzClient client;
+
+ public ClientAuthorizationContext(AccessToken authzToken, Map<String, PolicyEnforcerConfig.PathConfig> paths, AuthzClient client) {
+ super(authzToken, paths);
+ this.client = client;
+ }
+
+ public ClientAuthorizationContext(AuthzClient client) {
+ this.client = client;
+ }
+
+ public AuthzClient getClient() {
+ return client;
+ }
+}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java
index 536b188..fc24f0e 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java
@@ -48,7 +48,7 @@ public class ProtectionResource {
public TokenIntrospectionResponse introspectRequestingPartyToken(String rpt) {
return this.http.<TokenIntrospectionResponse>post("/protocol/openid-connect/token/introspect")
.authentication()
- .oauth2ClientCredentials()
+ .client()
.form()
.param("token_type_hint", "requesting_party_token")
.param("token", rpt)
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/util/Http.java b/authz/client/src/main/java/org/keycloak/authorization/client/util/Http.java
index 8bd9c07..f72e6b7 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/util/Http.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/util/Http.java
@@ -18,6 +18,7 @@
package org.keycloak.authorization.client.util;
import org.apache.http.client.methods.RequestBuilder;
+import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.ServerConfiguration;
@@ -29,10 +30,12 @@ import java.net.URI;
public class Http {
private final Configuration configuration;
+ private final ClientAuthenticator authenticator;
private ServerConfiguration serverConfiguration;
- public Http(Configuration configuration) {
+ public Http(Configuration configuration, ClientAuthenticator authenticator) {
this.configuration = configuration;
+ this.authenticator = authenticator;
}
public <R> HttpMethod<R> get(String path) {
@@ -60,7 +63,7 @@ public class Http {
}
private <R> HttpMethod<R> method(RequestBuilder builder) {
- return new HttpMethod(this.configuration, builder);
+ return new HttpMethod(this.configuration, authenticator, builder);
}
public void setServerConfiguration(ServerConfiguration serverConfiguration) {
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 10d450e..9a7e51a 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
@@ -17,6 +17,12 @@
*/
package org.keycloak.authorization.client.util;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
@@ -27,33 +33,30 @@ import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
+import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
-import java.io.UnsupportedEncodingException;
-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>
*/
public class HttpMethod<R> {
private final HttpClient httpClient;
+ private final ClientAuthenticator authenticator;
private final RequestBuilder builder;
protected final Configuration configuration;
protected final HashMap<String, String> headers;
protected final HashMap<String, String> params;
private HttpMethodResponse<R> response;
- public HttpMethod(Configuration configuration, RequestBuilder builder) {
- this(configuration, builder, new HashMap<String, String>(), new HashMap<String, String>());
+ public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder) {
+ this(configuration, authenticator, builder, new HashMap<String, String>(), new HashMap<String, String>());
}
- public HttpMethod(Configuration configuration, RequestBuilder builder, HashMap<String, String> params, HashMap<String, String> headers) {
+ public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder, HashMap<String, String> params, HashMap<String, String> headers) {
this.configuration = configuration;
this.httpClient = configuration.getHttpClient();
+ this.authenticator = authenticator;
this.builder = builder;
this.params = params;
this.headers = headers;
@@ -121,7 +124,7 @@ public class HttpMethod<R> {
}
public HttpMethodAuthenticator<R> authentication() {
- return new HttpMethodAuthenticator<R>(this);
+ return new HttpMethodAuthenticator<R>(this, authenticator);
}
public HttpMethod<R> param(String name, String value) {
@@ -136,7 +139,7 @@ public class HttpMethod<R> {
}
public HttpMethod<R> form() {
- return new HttpMethod<R>(this.configuration, this.builder, this.params, this.headers) {
+ return new HttpMethod<R>(this.configuration, authenticator, this.builder, this.params, this.headers) {
@Override
protected void preExecute(RequestBuilder builder) {
if (params != null) {
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java
index d67090a..8807d39 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java
@@ -18,6 +18,7 @@
package org.keycloak.authorization.client.util;
import org.keycloak.OAuth2Constants;
+import org.keycloak.authorization.client.ClientAuthenticator;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -25,26 +26,24 @@ import org.keycloak.OAuth2Constants;
public class HttpMethodAuthenticator<R> {
private final HttpMethod<R> method;
+ private final ClientAuthenticator authenticator;
- public HttpMethodAuthenticator(HttpMethod<R> method) {
+ public HttpMethodAuthenticator(HttpMethod<R> method, ClientAuthenticator authenticator) {
this.method = method;
+ this.authenticator = authenticator;
}
- public HttpMethod<R> oauth2ClientCredentials() {
+ public HttpMethod<R> client() {
this.method.params.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
- configureClientCredentials();
+ authenticator.configureClientCredentials(this.method.params, this.method.headers);
return this.method;
}
public HttpMethod<R> oauth2ResourceOwnerPassword(String userName, String password) {
+ client();
this.method.params.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
this.method.params.put("username", userName);
this.method.params.put("password", password);
- configureClientCredentials();
return this.method;
}
-
- private void configureClientCredentials() {
- this.method.configuration.getClientAuthenticator().configureClientCredentials(this.method.params, this.method.headers);
- }
}
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java
index 428ba07..a9a53f4 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java
+++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java
@@ -17,19 +17,18 @@
*/
package org.keycloak.example.photoz.admin;
-import org.keycloak.example.photoz.entity.Album;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
+
+import org.keycloak.example.photoz.entity.Album;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -37,14 +36,9 @@ import java.util.List;
@Path("/admin/album")
public class AdminAlbumService {
- public static final String SCOPE_ADMIN_ALBUM_MANAGE = "urn:photoz.com:scopes:album:admin:manage";
-
@Inject
private EntityManager entityManager;
- @Context
- private HttpHeaders headers;
-
@GET
@Produces("application/json")
public Response findAll() {
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 7dd6b24..129a11a 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
@@ -1,15 +1,14 @@
package org.keycloak.example.photoz.album;
+import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient;
-import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.example.photoz.ErrorResponse;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.example.photoz.util.Transaction;
-import org.keycloak.representations.adapters.config.AdapterConfig;
-import org.keycloak.util.JsonSerialization;
import javax.inject.Inject;
import javax.persistence.EntityManager;
@@ -35,7 +34,6 @@ import java.util.Set;
public class AlbumService {
public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view";
- public static final String SCOPE_ALBUM_CREATE = "urn:photoz.com:scopes:album:create";
public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete";
@Inject
@@ -44,12 +42,6 @@ public class AlbumService {
@Context
private HttpServletRequest request;
- private AuthzClient authzClient;
-
- public AlbumService() {
-
- }
-
@POST
@Consumes("application/json")
public Response create(Album newAlbum) {
@@ -142,17 +134,14 @@ public class AlbumService {
}
private AuthzClient getAuthzClient() {
- if (this.authzClient == null) {
- try {
- AdapterConfig adapterConfig = JsonSerialization.readValue(this.request.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"), AdapterConfig.class);
- Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), null);
-
- this.authzClient = AuthzClient.create(configuration);
- } catch (Exception e) {
- throw new RuntimeException("Could not create authorization client.", e);
- }
- }
+ return getAuthorizationContext().getClient();
+ }
+
+ private ClientAuthorizationContext getAuthorizationContext() {
+ return ClientAuthorizationContext.class.cast(getKeycloakSecurityContext().getAuthorizationContext());
+ }
- return this.authzClient;
+ private KeycloakSecurityContext getKeycloakSecurityContext() {
+ return KeycloakSecurityContext.class.cast(request.getAttribute(KeycloakSecurityContext.class.getName()));
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
index 80fb84a..665fe8f 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
@@ -65,9 +65,9 @@ public class AbstractPermissionService {
return request.stream().map(request1 -> {
String resourceSetId = request1.getResourceSetId();
String resourceSetName = request1.getResourceSetName();
- boolean resourceNotProvider = resourceSetId == null && resourceSetName == null;
+ boolean resourceNotProvided = resourceSetId == null && resourceSetName == null;
- if (resourceNotProvider) {
+ if (resourceNotProvided) {
if ((request1.getScopes() == null || request1.getScopes().isEmpty())) {
throw new ErrorResponseException("invalid_resource_set_id", "Resource id or name not provided.", Response.Status.BAD_REQUEST);
}
@@ -75,7 +75,7 @@ public class AbstractPermissionService {
Resource resource = null;
- if (!resourceNotProvider) {
+ if (!resourceNotProvided) {
if (resourceSetId != null) {
resource = storeFactory.getResourceStore().findById(resourceSetId, resourceServer.getId());
} else {
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
index b0aeb5d..bb21e50 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
@@ -112,14 +112,17 @@
},
{
"clientId": "photoz-restful-api",
- "secret": "secret",
"enabled": true,
"baseUrl": "/photoz-restful-api",
"authorizationServicesEnabled" : true,
"redirectUris": [
"/photoz-restful-api/*"
],
- "webOrigins" : ["*"]
+ "webOrigins" : ["*"],
+ "clientAuthenticatorType": "client-jwt",
+ "attributes" : {
+ "jwt.credential.certificate" : "MIICqTCCAZECBgFT0Ngs/DANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1zZWN1cmUtcG9ydGFsMB4XDTE2MDQwMTA4MDA0MVoXDTI2MDQwMTA4MDIyMVowGDEWMBQGA1UEAwwNc2VjdXJlLXBvcnRhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJa4GixpmzP511AmI0eLPLORyJwXS8908MUvdG3hmh8jMOIhe28XjIFeZSY09vFxh22F2SUMjxU/B2Hw4PDJUkebuNR7rXhOIYCJAo6eEZzjSBY/wngFtfm74zJ/eLCobBtDvIld7jobdHTfE1Oz9+GzvtG0k7cm7ubrLT0J4I1UsFZj3b//3wa+O0vNaTwHC1Jz/m59VbtXqyO4xEzIdl416cnGCmEmk5qd5h1de2UoLi/CTad8HftIJhzN1qhlySzW/9Ha70aYlDH2hiibDsXDTrNaMdaaLik7I8Rv/nIbggysG863PKZo8wknDe62QctH5VYSSktiy4gjSJkGh7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZnnx+AHQ8txugGcFK8gWjildDgk+v31fBHBDvmLQaSzsUaIOJaK4wnlwUI+VfR46HmBXhjlDCobFLUptd+kz0G7xapcIn3b5jLrySUUD7L+LAp1vNOQU4mKhTGS3IEvNB73D3GH9rQ+M3KEcoN3f99fNKqKsUdxbmZqGf4VOQ57PUfLBw4PJJGlROPosBc7ivPRyeYnKekhoCTynq30BAD1FA1BA8ppcY4ZVGADPTAgMJxpglpFY9LiqCwdLAGW1ttnsyIJ7DpT+kybhhk7c+MU7gyQdv8xPnMR0bSCB9hndowgBn5oZ393aMscwMNCzwJ0aWBs1sUyn3X0RIsu9Jg=="
+ }
}
]
}
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 cd4fdba..a1230d8 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
@@ -1,6 +1,8 @@
package org.keycloak.example.photoz.album;
+import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ScopeRepresentation;
@@ -38,7 +40,6 @@ public class AlbumService {
private static volatile long nextId = 0;
public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view";
- public static final String SCOPE_ALBUM_CREATE = "urn:photoz.com:scopes:album:create";
public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete";
@Inject
@@ -47,12 +48,6 @@ public class AlbumService {
@Context
private HttpServletRequest request;
- private AuthzClient authzClient;
-
- public AlbumService() {
-
- }
-
@POST
@Consumes("application/json")
public Response create(Album newAlbum, @QueryParam("user") String username) {
@@ -148,17 +143,14 @@ public class AlbumService {
}
private AuthzClient getAuthzClient() {
- if (this.authzClient == null) {
- try {
- AdapterConfig adapterConfig = JsonSerialization.readValue(this.request.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"), AdapterConfig.class);
- Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), null);
-
- this.authzClient = AuthzClient.create(configuration);
- } catch (Exception e) {
- throw new RuntimeException("Could not create authorization client.", e);
- }
- }
+ return getAuthorizationContext().getClient();
+ }
+
+ private ClientAuthorizationContext getAuthorizationContext() {
+ return ClientAuthorizationContext.class.cast(getKeycloakSecurityContext().getAuthorizationContext());
+ }
- return this.authzClient;
+ private KeycloakSecurityContext getKeycloakSecurityContext() {
+ return KeycloakSecurityContext.class.cast(request.getAttribute(KeycloakSecurityContext.class.getName()));
}
-}
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/keystore.jks b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/keystore.jks
new file mode 100644
index 0000000..399be7a
Binary files /dev/null and b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/keystore.jks differ
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 a3ac697..5b41d26 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
@@ -6,7 +6,14 @@
"resource": "photoz-restful-api",
"bearer-only" : true,
"credentials": {
- "secret": "secret"
+ "jwt": {
+ "client-key-password": "password",
+ "client-keystore-file": "classpath:keystore.jks",
+ "client-keystore-password": "password",
+ "client-key-alias": "secure-portal",
+ "token-timeout": 10,
+ "client-keystore-type": "jks"
+ }
},
"policy-enforcer": {
"user-managed-access" : {},
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
new file mode 100644
index 0000000..2e9086a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.authz;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
+import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.ClientAuthenticator;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.representation.AuthorizationRequest;
+import org.keycloak.authorization.client.representation.AuthorizationResponse;
+import org.keycloak.authorization.client.representation.PermissionRequest;
+import org.keycloak.authorization.client.representation.PermissionResponse;
+import org.keycloak.authorization.client.representation.RegistrationResponse;
+import org.keycloak.authorization.client.representation.ResourceRepresentation;
+import org.keycloak.authorization.client.resource.ProtectionResource;
+import org.keycloak.authorization.client.util.HttpResponseException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.authorization.Permission;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AuthzClientCredentialsTest extends AbstractKeycloakTest {
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ testRealms.add(configureRealm(RealmBuilder.create().name("authz-client-jwt-test"), ClientBuilder.create()
+ .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ==")
+ .authenticatorType(JWTClientAuthenticator.PROVIDER_ID))
+ .build());
+ testRealms.add(configureRealm(RealmBuilder.create().name("authz-test"), ClientBuilder.create().secret("secret")).build());
+ }
+
+ @Before
+ public void beforeAbstractKeycloakTest() throws Exception {
+ super.beforeAbstractKeycloakTest();
+ testContext.getTestRealmReps().forEach(realmRepresentation -> {
+ Keycloak adminClient = getAdminClient();
+ ClientsResource clients = adminClient.realm(realmRepresentation.getRealm()).clients();
+ ClientRepresentation client = clients.findByClientId("resource-server-test").get(0);
+
+ client.setAuthorizationServicesEnabled(false);
+
+ clients.get(client.getId()).update(client);
+
+ client.setAuthorizationServicesEnabled(true);
+
+ clients.get(client.getId()).update(client);
+
+ AuthorizationResource authorization = clients.get(client.getId()).authorization();
+ ResourceServerRepresentation settings = authorization.getSettings();
+
+ settings.setAllowRemoteResourceManagement(true);
+
+ authorization.update(settings);
+ });
+ }
+
+ @Test
+ public void testSuccessfulJWTAuthentication() {
+ assertAccessProtectionAPI(getAuthzClient("keycloak-with-jwt-authentication.json").protection());
+ }
+
+ @Test
+ public void testSuccessfulAuthorizationRequest() throws Exception {
+ AuthzClient authzClient = getAuthzClient("keycloak-with-jwt-authentication.json");
+ ProtectionResource protection = authzClient.protection();
+ PermissionRequest request = new PermissionRequest();
+
+ request.setResourceSetName("Default Resource");
+
+ PermissionResponse ticketResponse = protection.permission().forResource(request);
+ String ticket = ticketResponse.getTicket();
+
+ AuthorizationResponse authorizationResponse = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
+ String rpt = authorizationResponse.getRpt();
+
+ assertNotNull(rpt);
+
+ AccessToken accessToken = new JWSInput(rpt).readJsonContent(AccessToken.class);
+
+ AccessToken.Authorization authorization = accessToken.getAuthorization();
+
+ assertNotNull(authorization);
+
+ List<Permission> permissions = authorization.getPermissions();
+
+ assertFalse(permissions.isEmpty());
+ assertEquals("Default Resource", permissions.get(0).getResourceSetName());
+ }
+
+ @Test
+ public void failUserWithoutUmaAuthorizationScope() throws Exception {
+ AuthzClient authzClient = getAuthzClient("keycloak-with-jwt-authentication.json");
+ ProtectionResource protection = authzClient.protection();
+ PermissionRequest request = new PermissionRequest();
+
+ request.setResourceSetName("Default Resource");
+
+ PermissionResponse ticketResponse = protection.permission().forResource(request);
+ String ticket = ticketResponse.getTicket();
+
+ try {
+ authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+ fail("Should fail because user does not have uma_authorization");
+ } catch (AuthorizationDeniedException cause) {
+ assertEquals(403, ((HttpResponseException) cause.getCause()).getStatusCode());
+ }
+ }
+
+ @Test
+ public void failJWTAuthentication() {
+ try {
+ getAuthzClient("keycloak-with-invalid-keys-jwt-authentication.json").protection();
+ fail("Should fail due to invalid signature");
+ } catch (HttpResponseException cause) {
+ assertEquals(400, cause.getStatusCode());
+ }
+ }
+
+ @Test
+ public void testSuccessfulClientSecret() {
+ ProtectionResource protection = getAuthzClient("default-keycloak.json").protection();
+ assertAccessProtectionAPI(protection);
+ }
+
+ private RealmBuilder configureRealm(RealmBuilder builder, ClientBuilder clientBuilder) {
+ return builder
+ .roles(RolesBuilder.create().realmRole(new RoleRepresentation("uma_authorization", "", false)))
+ .user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization"))
+ .user(UserBuilder.create().username("kolo").password("password"))
+ .client(clientBuilder.clientId("resource-server-test")
+ .authorizationServicesEnabled(true)
+ .redirectUris("http://localhost/resource-server-test")
+ .defaultRoles("uma_protection")
+ .directAccessGrants());
+ }
+
+ private void assertAccessProtectionAPI(ProtectionResource protection) {
+ ResourceRepresentation expected = new ResourceRepresentation("Resource A", Collections.emptySet());
+
+ String id = protection.resource().create(expected).getId();
+ RegistrationResponse response = protection.resource().findById(id);
+ ResourceRepresentation actual = response.getResourceDescription();
+
+ assertNotNull(actual);
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(id, actual.getId());
+ }
+
+ private AuthzClient getAuthzClient(String adapterConfig) {
+ KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getConfigurationStream(adapterConfig));
+
+ return AuthzClient.create(new Configuration(deployment.getAuthServerBaseUrl(), deployment.getRealm(), deployment.getResourceName(), deployment.getResourceCredentials(), deployment.getClient()), new ClientAuthenticator() {
+ @Override
+ public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
+ ClientCredentialsProviderUtils.setClientCredentials(deployment, requestHeaders, requestParams);
+ }
+ });
+ }
+
+ private InputStream getConfigurationStream(String adapterConfig) {
+ return getClass().getResourceAsStream("/authorization-test/" + adapterConfig);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/keycloak-with-invalid-keys-jwt-authentication.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/keycloak-with-invalid-keys-jwt-authentication.json
new file mode 100644
index 0000000..482741f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/keycloak-with-invalid-keys-jwt-authentication.json
@@ -0,0 +1,15 @@
+{
+ "realm": "authz-client-jwt-test",
+ "auth-server-url" : "http://localhost:8180/auth",
+ "resource" : "resource-server-test",
+ "credentials": {
+ "jwt": {
+ "client-keystore-file": "classpath:client-auth-test/keystore-client2.jks",
+ "client-keystore-type": "JKS",
+ "client-keystore-password": "storepass",
+ "client-key-alias": "clientkey",
+ "client-key-password": "keypass",
+ "token-expiration": 10
+ }
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/keycloak-with-jwt-authentication.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/keycloak-with-jwt-authentication.json
new file mode 100644
index 0000000..0fad5ef
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/keycloak-with-jwt-authentication.json
@@ -0,0 +1,15 @@
+{
+ "realm": "authz-client-jwt-test",
+ "auth-server-url" : "http://localhost:8180/auth",
+ "resource" : "resource-server-test",
+ "credentials": {
+ "jwt": {
+ "client-keystore-file": "classpath:client-auth-test/keystore-client1.jks",
+ "client-keystore-type": "JKS",
+ "client-keystore-password": "storepass",
+ "client-key-alias": "clientkey",
+ "client-key-password": "keypass",
+ "token-expiration": 10
+ }
+ }
+}
\ No newline at end of file