Decouple the policy enforcer from adapters and provide a separate library
Closes keycloak#17353
This commit is contained in:
parent
6e58f5a5d5
commit
a30b6842a6
71 changed files with 1861 additions and 1387 deletions
|
@ -82,6 +82,11 @@
|
|||
<artifactId>keycloak-authz-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
|
|
|
@ -20,7 +20,6 @@ package org.keycloak.adapters;
|
|||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
@ -28,6 +27,7 @@ import org.keycloak.common.enums.RelativeUrlsUsed;
|
|||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -17,11 +17,18 @@
|
|||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -89,4 +96,21 @@ public class AdapterUtils {
|
|||
public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> createPrincipal(KeycloakDeployment deployment, RefreshableKeycloakSecurityContext securityContext) {
|
||||
return new KeycloakPrincipal<>(getPrincipalName(deployment, securityContext.getToken()), securityContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use directly from your JEE apps to avoid HttpClient linkage errors! Instead use the method {@link #setClientCredentials(KeycloakDeployment, Map, Map)}
|
||||
*/
|
||||
public static void setClientCredentials(KeycloakDeployment deployment, HttpPost post, List<NameValuePair> formparams) {
|
||||
Map<String, String> reqHeaders = new HashMap<>();
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment.getAdapterConfig(), deployment.getClientAuthenticator(), reqHeaders, reqParams);
|
||||
|
||||
for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
|
||||
post.setHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> param : reqParams.entrySet()) {
|
||||
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package org.keycloak.adapters;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
||||
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
|
@ -155,7 +157,7 @@ public class AuthenticatedActionsHandler {
|
|||
}
|
||||
try {
|
||||
OIDCHttpFacade facade = (OIDCHttpFacade) this.facade;
|
||||
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
||||
AuthorizationContext authorizationContext = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
||||
|
||||
if (session != null) {
|
||||
|
|
|
@ -27,12 +27,9 @@ import org.apache.http.message.BasicNameValuePair;
|
|||
import org.apache.http.util.EntityUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
|
@ -97,7 +94,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
|
|||
formparams.add(new BasicNameValuePair("username", username));
|
||||
formparams.add(new BasicNameValuePair("password", password));
|
||||
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
||||
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
|
@ -180,8 +182,9 @@ public class BearerTokenRequestAuthenticator {
|
|||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade facade) {
|
||||
OIDCHttpFacade oidcFacade = (OIDCHttpFacade) facade;
|
||||
if (deployment.getPolicyEnforcer() != null) {
|
||||
deployment.getPolicyEnforcer().enforce(OIDCHttpFacade.class.cast(facade));
|
||||
deployment.getPolicyEnforcer().enforce(new HttpAuthzRequest(oidcFacade), new HttpAuthzResponse(oidcFacade));
|
||||
return true;
|
||||
}
|
||||
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.apache.http.client.HttpClient;
|
|||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
|
@ -30,6 +29,7 @@ import org.keycloak.common.enums.SslRequired;
|
|||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -104,6 +104,7 @@ public class KeycloakDeployment {
|
|||
|
||||
protected boolean delegateBearerErrorResponseSending = false;
|
||||
protected boolean verifyTokenAudience = false;
|
||||
private AdapterConfig adapterConfig;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
|
@ -159,6 +160,8 @@ public class KeycloakDeployment {
|
|||
// We have absolute URI in config
|
||||
relativeUrls = RelativeUrlsUsed.NEVER;
|
||||
}
|
||||
|
||||
this.adapterConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -598,4 +601,8 @@ public class KeycloakDeployment {
|
|||
public void setClient(Callable<HttpClient> callable) {
|
||||
client = callable;
|
||||
}
|
||||
|
||||
public AdapterConfig getAdapterConfig() {
|
||||
return adapterConfig;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import static org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils.bootstrapClientAuthenticator;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
|
@ -97,7 +98,7 @@ public class KeycloakDeploymentBuilder {
|
|||
if (adapterConfig.getPrincipalAttribute() != null) deployment.setPrincipalAttribute(adapterConfig.getPrincipalAttribute());
|
||||
|
||||
deployment.setResourceCredentials(adapterConfig.getCredentials());
|
||||
deployment.setClientAuthenticator(ClientCredentialsProviderUtils.bootstrapClientAuthenticator(deployment));
|
||||
deployment.setClientAuthenticator(bootstrapClientAuthenticator(adapterConfig));
|
||||
|
||||
deployment.setPublicClient(adapterConfig.isPublicClient());
|
||||
deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());
|
||||
|
@ -152,7 +153,14 @@ public class KeycloakDeploymentBuilder {
|
|||
if (policyEnforcer == null) {
|
||||
synchronized (deployment) {
|
||||
if (policyEnforcer == null) {
|
||||
policyEnforcer = new PolicyEnforcer(deployment, adapterConfig);
|
||||
policyEnforcer = PolicyEnforcer.builder()
|
||||
.authServerUrl(adapterConfig.getAuthServerUrl())
|
||||
.realm(adapterConfig.getRealm())
|
||||
.clientId(adapterConfig.getResource())
|
||||
.bearerOnly(adapterConfig.isBearerOnly())
|
||||
.credentialProvider(deployment.getClientAuthenticator())
|
||||
.enforcerConfig(policyEnforcerConfig)
|
||||
.httpClient(deployment.getClient()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ import java.security.PublicKey;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
|
@ -31,6 +29,8 @@ import org.keycloak.common.util.StreamUtil;
|
|||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
|
@ -75,7 +74,7 @@ public class ServerRequest {
|
|||
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
HttpPost post = new HttpPost(uri);
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
@ -104,7 +103,7 @@ public class ServerRequest {
|
|||
}
|
||||
|
||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
@ -160,7 +159,7 @@ public class ServerRequest {
|
|||
}
|
||||
|
||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
@ -202,7 +201,7 @@ public class ServerRequest {
|
|||
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
|
||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
@ -257,7 +256,7 @@ public class ServerRequest {
|
|||
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_CLUSTER_HOST, host));
|
||||
|
||||
HttpPost post = new HttpPost(endpointUrl);
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
|
|
@ -1,374 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
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.AccessToken.Authorization;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
||||
private static final String HTTP_METHOD_DELETE = "DELETE";
|
||||
|
||||
private final PolicyEnforcer policyEnforcer;
|
||||
|
||||
protected AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||
EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
if (securityContext == null) {
|
||||
httpFacade.getResponse().sendError(401, "Invalid bearer");
|
||||
}
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
Request request = httpFacade.getRequest();
|
||||
PathConfig pathConfig = getPathConfig(request);
|
||||
|
||||
if (securityContext == null) {
|
||||
if (!isDefaultAccessDeniedUri(request)) {
|
||||
if (pathConfig != null) {
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
} else {
|
||||
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
|
||||
}
|
||||
} else {
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
}
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
|
||||
}
|
||||
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
}
|
||||
|
||||
if (!challenge(pathConfig, methodConfig, httpFacade)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||
}
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
protected abstract boolean challenge(PathConfig pathConfig, MethodConfig methodConfig, OIDCHttpFacade facade);
|
||||
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
|
||||
Request request = httpFacade.getRequest();
|
||||
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Authorization authorization = accessToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasPermission = false;
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : grantedPermissions) {
|
||||
if (permission.getResourceId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||
policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
|
||||
}
|
||||
|
||||
return hasValidClaims(permission, claims);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
hasPermission = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasValidClaims(Permission permission, Map<String, List<String>> claims) {
|
||||
Map<String, Set<String>> grantedClaims = permission.getClaims();
|
||||
|
||||
if (grantedClaims != null) {
|
||||
if (claims.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Entry<String, Set<String>> entry : grantedClaims.entrySet()) {
|
||||
List<String> requestClaims = claims.get(entry.getKey());
|
||||
|
||||
if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
|
||||
httpFacade.getResponse().sendError(403);
|
||||
}
|
||||
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return policyEnforcer.getClient();
|
||||
}
|
||||
|
||||
protected PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return policyEnforcer.getEnforcerConfig();
|
||||
}
|
||||
|
||||
protected PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(Request request) {
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
||||
List<String> requiredScopes = methodConfig.getScopes();
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
|
||||
if (allowedScopes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig.ScopeEnforcementMode enforcementMode = methodConfig.getScopesEnforcementMode();
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ALL.equals(enforcementMode)) {
|
||||
return allowedScopes.containsAll(requiredScopes);
|
||||
}
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ANY.equals(enforcementMode)) {
|
||||
for (String requiredScope : requiredScopes) {
|
||||
if (allowedScopes.contains(requiredScope)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requiredScopes.isEmpty();
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new ClientAuthorizationContext(getAuthzClient()) {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResourcePermission(String resourceName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScopePermission(String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Permission> getPermissions() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
return granted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getPath(Request request) {
|
||||
return request.getRelativePath();
|
||||
}
|
||||
|
||||
private MethodConfig getRequiredScopes(PathConfig pathConfig, Request request) {
|
||||
String method = request.getMethod();
|
||||
|
||||
for (MethodConfig methodConfig : pathConfig.getMethods()) {
|
||||
if (methodConfig.getMethod().equals(method)) {
|
||||
return methodConfig;
|
||||
}
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
|
||||
methodConfig.setMethod(request.getMethod());
|
||||
List scopes = new ArrayList<>();
|
||||
|
||||
if (Boolean.TRUE.equals(getEnforcerConfig().getHttpMethodAsScope())) {
|
||||
scopes.add(request.getMethod());
|
||||
} else {
|
||||
scopes.addAll(pathConfig.getScopes());
|
||||
}
|
||||
|
||||
methodConfig.setScopes(scopes);
|
||||
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
|
||||
|
||||
return methodConfig;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
// first we try a match using resource id
|
||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||
|
||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||
}
|
||||
|
||||
return resourceMatch;
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceId().equals(actualPathConfig.getId());
|
||||
}
|
||||
|
||||
private PathConfig getPathConfig(Request request) {
|
||||
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
|
||||
}
|
||||
|
||||
protected Map<String, List<String>> resolveClaims(PathConfig pathConfig, OIDCHttpFacade httpFacade) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
resolveClaims(claims, getEnforcerConfig().getClaimInformationPointConfig(), httpFacade);
|
||||
resolveClaims(claims, pathConfig.getClaimInformationPointConfig(), httpFacade);
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private void resolveClaims(Map<String, List<String>> claims, Map<String, Map<String, Object>> claimInformationPointConfig, HttpFacade httpFacade) {
|
||||
if (claimInformationPointConfig != null) {
|
||||
for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
|
||||
ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
|
||||
|
||||
if (factory != null) {
|
||||
claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(KeycloakAdapterPolicyEnforcer.class);
|
||||
|
||||
public KeycloakAdapterPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
super(policyEnforcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
|
||||
AccessToken original = accessToken;
|
||||
|
||||
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade, claims);
|
||||
|
||||
if (accessToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessToken.Authorization authorization = original.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
authorization = new AccessToken.Authorization();
|
||||
authorization.setPermissions(new ArrayList<Permission>());
|
||||
}
|
||||
|
||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
Collection<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
|
||||
for (Permission newPermission : newPermissions) {
|
||||
if (!grantedPermissions.contains(newPermission)) {
|
||||
grantedPermissions.add(newPermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
return super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
|
||||
if (isBearerAuthorization(httpFacade)) {
|
||||
HttpFacade.Response response = httpFacade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, httpFacade);
|
||||
|
||||
if (ticket != null) {
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
|
||||
.append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
|
||||
} else {
|
||||
response.setStatus(403);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending challenge");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAccessDenied(OIDCHttpFacade facade) {
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
response.setStatus(302);
|
||||
response.setHeader("Location", accessDeniedPath);
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
String accessTokenString = securityContext.getTokenString();
|
||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest();
|
||||
|
||||
if (isBearerAuthorization(httpFacade) || accessToken.getAuthorization() != null) {
|
||||
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
|
||||
}
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
|
||||
authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
|
||||
}
|
||||
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.setRpt(accessTokenString);
|
||||
}
|
||||
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
AuthorizationResponse authzResponse;
|
||||
|
||||
if (isBearerAuthorization(httpFacade)) {
|
||||
authzRequest.setSubjectToken(accessTokenString);
|
||||
authzResponse = getAuthzClient().authorization().authorize(authzRequest);
|
||||
} else {
|
||||
authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
|
||||
}
|
||||
|
||||
if (authzResponse != null) {
|
||||
return AdapterTokenVerifier.verifyToken(authzResponse.getToken(), deployment);
|
||||
}
|
||||
} catch (AuthorizationDeniedException ignore) {
|
||||
LOGGER.debug("Authorization denied", ignore);
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Authorization failed", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, OIDCHttpFacade httpFacade) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
permissionRequest.setClaims(claims);
|
||||
}
|
||||
|
||||
return permission.create(permissionRequest).getTicket();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
|
||||
List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
|
||||
|
||||
if (authHeaders != null) {
|
||||
for (String authHeader : authHeaders) {
|
||||
String[] split = authHeader.trim().split("\\s+");
|
||||
if (split == null || split.length != 2) continue;
|
||||
if (!split[0].equalsIgnoreCase("Bearer")) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return getPolicyEnforcer().getDeployment().isBearerOnly();
|
||||
}
|
||||
}
|
|
@ -1,382 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
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.resource.ProtectedResource;
|
||||
import org.keycloak.common.util.PathMatcher;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
|
||||
private final KeycloakDeployment deployment;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final PathConfigMatcher pathMatcher;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
|
||||
|
||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient());
|
||||
this.authzClient = AuthzClient.create(configuration, new ClientAuthenticator() {
|
||||
@Override
|
||||
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
Map<String, String> formparams = new HashMap<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, formparams);
|
||||
for (Entry<String, String> param : formparams.entrySet()) {
|
||||
requestParams.put(param.getKey(), Arrays.asList(param.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
pathMatcher = new PathConfigMatcher(paths, enforcerConfig, authzClient);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configurations:");
|
||||
for (PathConfig pathConfig : this.paths.values()) {
|
||||
LOGGER.debug(pathConfig);
|
||||
}
|
||||
}
|
||||
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(OIDCHttpFacade facade) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||
LOGGER.debugv("Returning authorization context with permissions:");
|
||||
for (Permission permission : context.getPermissions()) {
|
||||
LOGGER.debug(permission);
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return enforcerConfig;
|
||||
}
|
||||
|
||||
public AuthzClient getClient() {
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
public PathConfigMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
public KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
|
||||
return claimInformationPointProviderFactories;
|
||||
}
|
||||
|
||||
private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
|
||||
|
||||
for (ClaimInformationPointProviderFactory factory : loader) {
|
||||
factory.init(this);
|
||||
|
||||
claimInformationPointProviderFactories.put(factory.getName(), factory);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
boolean loadPathsFromServer = !enforcerConfig.getLazyLoadPaths();
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
loadPathsFromServer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
Map<String, PathConfig> paths = configureAllPathsForResourceServer(protectedResource);
|
||||
|
||||
paths.putAll(configureDefinedPaths(protectedResource, enforcerConfig));
|
||||
|
||||
return paths;
|
||||
} else {
|
||||
LOGGER.info("Paths provided in configuration.");
|
||||
return configureDefinedPaths(protectedResource, enforcerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
ResourceRepresentation resource;
|
||||
String resourceName = pathConfig.getName();
|
||||
String path = pathConfig.getPath();
|
||||
|
||||
if (resourceName != null) {
|
||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
||||
resource = protectedResource.findByName(resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
List<ResourceRepresentation> resources = protectedResource.findByUri(path);
|
||||
|
||||
if (resources.isEmpty()) {
|
||||
resources = protectedResource.findByMatchingUri(path);
|
||||
}
|
||||
|
||||
if (resources.size() == 1) {
|
||||
resource = resources.get(0);
|
||||
} else if (resources.size() > 1) {
|
||||
throw new RuntimeException("Multiple resources found with the same uri");
|
||||
} else {
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (resource != null) {
|
||||
pathConfig.setId(resource.getId());
|
||||
// if the resource is statically bound to a resource it means the config can not be invalidated
|
||||
if (resourceName != null) {
|
||||
pathConfig.setStatic(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
pathConfig.setStatic(true);
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths.values()) {
|
||||
if (current.getPath().equals(pathConfig.getPath())) {
|
||||
existingPath = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPath == null) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
} else {
|
||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
if (!enforcerConfig.getLazyLoadPaths()) {
|
||||
for (String id : protectedResource.findAll()) {
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUris() != null && !resourceDescription.getUris().isEmpty()) {
|
||||
for(PathConfig pathConfig : PathConfig.createPathConfigs(resourceDescription)) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
public static class PathConfigMatcher extends PathMatcher<PathConfig> {
|
||||
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathCache pathCache;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
public PathConfigMatcher(Map<String, PathConfig> paths, PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
|
||||
this.paths = paths;
|
||||
this.enforcerConfig = enforcerConfig;
|
||||
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
|
||||
|
||||
if (cacheConfig == null) {
|
||||
cacheConfig = new PathCacheConfig();
|
||||
}
|
||||
|
||||
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan(), paths);
|
||||
this.authzClient = authzClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathConfig matches(String targetUri) {
|
||||
PathConfig pathConfig = pathCache.get(targetUri);
|
||||
|
||||
if (pathCache.containsKey(targetUri) || pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
pathConfig = super.matches(targetUri);
|
||||
|
||||
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
|
||||
if ((pathConfig == null || pathConfig.isInvalidated() || pathConfig.getPath().contains("*"))) {
|
||||
try {
|
||||
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||
|
||||
if (matchingResources.isEmpty()) {
|
||||
// if this config is invalidated (e.g.: due to cache expiration) we remove and return null
|
||||
if (pathConfig != null && pathConfig.isInvalidated()) {
|
||||
paths.remove(targetUri);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Map<String, Map<String, Object>> cipConfig = null;
|
||||
PolicyEnforcerConfig.EnforcementMode enforcementMode = PolicyEnforcerConfig.EnforcementMode.ENFORCING;
|
||||
ResourceRepresentation targetResource = matchingResources.get(0);
|
||||
List<PolicyEnforcerConfig.MethodConfig> methodConfig = null;
|
||||
boolean isStatic = false;
|
||||
|
||||
if (pathConfig != null) {
|
||||
cipConfig = pathConfig.getClaimInformationPointConfig();
|
||||
enforcementMode = pathConfig.getEnforcementMode();
|
||||
methodConfig = pathConfig.getMethods();
|
||||
isStatic = pathConfig.isStatic();
|
||||
} else {
|
||||
for (PathConfig existingPath : paths.values()) {
|
||||
if (targetResource.getId().equals(existingPath.getId())
|
||||
&& existingPath.isStatic()
|
||||
&& !PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(existingPath.getEnforcementMode())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathConfig = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
if (cipConfig != null) {
|
||||
pathConfig.setClaimInformationPointConfig(cipConfig);
|
||||
}
|
||||
|
||||
if (methodConfig != null) {
|
||||
pathConfig.setMethods(methodConfig);
|
||||
}
|
||||
|
||||
pathConfig.setStatic(isStatic);
|
||||
pathConfig.setEnforcementMode(enforcementMode);
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCache.put(targetUri, pathConfig);
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPath(PathConfig entry) {
|
||||
return entry.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<PathConfig> getPaths() {
|
||||
return paths.values();
|
||||
}
|
||||
|
||||
public PathCache getPathCache() {
|
||||
return pathCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = authzClient.protection().resource();
|
||||
|
||||
// search by an exact match
|
||||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
// if exact match not found, try to obtain from current path the parent path.
|
||||
// if path is /resource/1/test and pattern from pathConfig is /resource/{id}/*, parent path is /resource/1
|
||||
// this logic allows to match sub resources of a resource instance (/resource/1) to the parent resource,
|
||||
// so any permission granted to parent also applies to sub resources
|
||||
if (search.isEmpty()) {
|
||||
search = resource.findByUri(buildUriFromTemplate(originalConfig.getPath(), path, true));
|
||||
}
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeFromCache(String pathConfig) {
|
||||
pathCache.remove(pathConfig);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -26,11 +26,9 @@ import org.apache.http.client.methods.HttpPost;
|
|||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -96,7 +94,7 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||
formparams.add(new BasicNameValuePair(OAuth2Constants.SCOPE, scope));
|
||||
}
|
||||
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
|
@ -154,7 +152,7 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||
HttpPost post = new HttpPost(logoutUri);
|
||||
|
||||
List<NameValuePair> formparams = new ArrayList<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.pep;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpAuthzRequest implements HttpRequest {
|
||||
|
||||
private final TokenPrincipal tokenPrincipal;
|
||||
private final OIDCHttpFacade oidcFacade;
|
||||
|
||||
public HttpAuthzRequest(OIDCHttpFacade oidcFacade) {
|
||||
this.oidcFacade = oidcFacade;
|
||||
tokenPrincipal = new TokenPrincipal() {
|
||||
@Override
|
||||
public String getRawToken() {
|
||||
KeycloakSecurityContext securityContext = oidcFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return oidcFacade.getSecurityContext().getTokenString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken getToken() {
|
||||
KeycloakSecurityContext securityContext = oidcFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return securityContext.getToken();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRelativePath() {
|
||||
return oidcFacade.getRequest().getRelativePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return oidcFacade.getRequest().getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return oidcFacade.getRequest().getURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
return oidcFacade.getRequest().getHeaders(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String name) {
|
||||
String queryParamValue = oidcFacade.getRequest().getQueryParamValue(name);
|
||||
|
||||
if (queryParamValue != null) {
|
||||
return queryParamValue;
|
||||
}
|
||||
|
||||
return oidcFacade.getRequest().getFirstParam(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCookieValue(String name) {
|
||||
Cookie cookie = oidcFacade.getRequest().getCookie(name);
|
||||
|
||||
if (cookie == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cookie.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return oidcFacade.getRequest().getRemoteAddr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return oidcFacade.getRequest().isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return oidcFacade.getRequest().getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(boolean buffered) {
|
||||
return oidcFacade.getRequest().getInputStream(buffered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenPrincipal getPrincipal() {
|
||||
return tokenPrincipal;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.pep;
|
||||
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.authorization.spi.HttpResponse;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpAuthzResponse implements HttpResponse {
|
||||
|
||||
private OIDCHttpFacade oidcFacade;
|
||||
|
||||
public HttpAuthzResponse(OIDCHttpFacade oidcFacade) {
|
||||
this.oidcFacade = oidcFacade;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int statusCode) {
|
||||
oidcFacade.getResponse().setStatus(statusCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int code, String reason) {
|
||||
oidcFacade.getResponse().sendError(code, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
oidcFacade.getResponse().setHeader(name, value);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,19 +20,17 @@ package org.keycloak.adapters;
|
|||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.Configurable;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.params.CoreConnectionPNames;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientIdAndSecretCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientSecretCredentialsProvider;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -40,7 +38,6 @@ import static org.junit.Assert.assertFalse;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
|
|
@ -50,6 +50,10 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_4.0_spec</artifactId>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<artifactId>keycloak-authz-client</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>KeyCloak Authz: Client API</name>
|
||||
<name>Keycloak Authz: Client API</name>
|
||||
<description>KeyCloak AuthZ: Client API</description>
|
||||
|
||||
<properties>
|
||||
|
|
|
@ -91,18 +91,7 @@ public class AuthzClient {
|
|||
* @return a new instance
|
||||
*/
|
||||
public static AuthzClient create(Configuration configuration) {
|
||||
return new AuthzClient(configuration, configuration.getClientAuthenticator());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new instance.
|
||||
*
|
||||
* @param configuration the client configuration
|
||||
* @param authenticator the client authenticator
|
||||
* @return a new instance
|
||||
*/
|
||||
public static AuthzClient create(Configuration configuration, ClientAuthenticator authenticator) {
|
||||
return new AuthzClient(configuration, authenticator);
|
||||
return new AuthzClient(configuration);
|
||||
}
|
||||
|
||||
private final ServerConfiguration serverConfiguration;
|
||||
|
@ -242,7 +231,7 @@ public class AuthzClient {
|
|||
return this.configuration;
|
||||
}
|
||||
|
||||
private AuthzClient(Configuration configuration, ClientAuthenticator authenticator) {
|
||||
private AuthzClient(Configuration configuration) {
|
||||
if (configuration == null) {
|
||||
throw new IllegalArgumentException("Client configuration can not be null.");
|
||||
}
|
||||
|
@ -256,7 +245,7 @@ public class AuthzClient {
|
|||
configurationUrl = KeycloakUriBuilder.fromUri(configurationUrl).clone().path(AUTHZ_DISCOVERY_URL).build(configuration.getRealm()).toString();
|
||||
this.configuration = configuration;
|
||||
|
||||
this.http = new Http(configuration, authenticator != null ? authenticator : configuration.getClientAuthenticator());
|
||||
this.http = new Http(configuration, configuration.getClientCredentialsProvider());
|
||||
|
||||
try {
|
||||
this.serverConfiguration = this.http.<ServerConfiguration>get(configurationUrl)
|
||||
|
@ -265,8 +254,6 @@ public class AuthzClient {
|
|||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not obtain configuration from server [" + configurationUrl + "].", e);
|
||||
}
|
||||
|
||||
this.http.setServerConfiguration(this.serverConfiguration);
|
||||
}
|
||||
|
||||
private TokenCallable createPatSupplier(String userName, String password) {
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* 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.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClientAuthenticator {
|
||||
void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders);
|
||||
}
|
|
@ -17,14 +17,14 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -35,7 +35,7 @@ public class Configuration extends AdapterConfig {
|
|||
private HttpClient httpClient;
|
||||
|
||||
@JsonIgnore
|
||||
private ClientAuthenticator clientAuthenticator = createDefaultClientAuthenticator();
|
||||
private ClientCredentialsProvider clientCredentialsProvider;
|
||||
|
||||
public Configuration() {
|
||||
|
||||
|
@ -66,27 +66,18 @@ public class Configuration extends AdapterConfig {
|
|||
return httpClient;
|
||||
}
|
||||
|
||||
ClientAuthenticator getClientAuthenticator() {
|
||||
return this.clientAuthenticator;
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default client authenticator which uses HTTP BASIC and client id and secret to authenticate the client.
|
||||
*
|
||||
* @return the default client authenticator
|
||||
*/
|
||||
private ClientAuthenticator createDefaultClientAuthenticator() {
|
||||
return new ClientAuthenticator() {
|
||||
@Override
|
||||
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
String secret = (String) getCredentials().get("secret");
|
||||
public void setClientCredentialsProvider(ClientCredentialsProvider clientCredentialsProvider) {
|
||||
this.clientCredentialsProvider = clientCredentialsProvider;
|
||||
}
|
||||
|
||||
if (secret == null) {
|
||||
throw new RuntimeException("Client secret not provided.");
|
||||
}
|
||||
|
||||
requestHeaders.put("Authorization", BasicAuthHelper.RFC6749.createHeader(getResource(), secret));
|
||||
}
|
||||
};
|
||||
public ClientCredentialsProvider getClientCredentialsProvider() {
|
||||
if (clientCredentialsProvider == null) {
|
||||
clientCredentialsProvider = ClientCredentialsProviderUtils.bootstrapClientAuthenticator(this);
|
||||
}
|
||||
return clientCredentialsProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
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;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -28,10 +28,9 @@ import org.keycloak.authorization.client.representation.ServerConfiguration;
|
|||
public class Http {
|
||||
|
||||
private final Configuration configuration;
|
||||
private final ClientAuthenticator authenticator;
|
||||
private ServerConfiguration serverConfiguration;
|
||||
private final ClientCredentialsProvider authenticator;
|
||||
|
||||
public Http(Configuration configuration, ClientAuthenticator authenticator) {
|
||||
public Http(Configuration configuration, ClientCredentialsProvider authenticator) {
|
||||
this.configuration = configuration;
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
@ -55,8 +54,4 @@ public class Http {
|
|||
private <R> HttpMethod<R> method(RequestBuilder builder) {
|
||||
return new HttpMethod(this.configuration, authenticator, builder);
|
||||
}
|
||||
|
||||
public void setServerConfiguration(ServerConfiguration serverConfiguration) {
|
||||
this.serverConfiguration = serverConfiguration;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,28 +35,29 @@ 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 org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpMethod<R> {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final ClientAuthenticator authenticator;
|
||||
protected final RequestBuilder builder;
|
||||
protected final Configuration configuration;
|
||||
protected final Map<String, String> headers;
|
||||
protected final Map<String, List<String>> params;
|
||||
private static final Logger logger = Logger.getLogger(HttpMethod.class.getName());
|
||||
|
||||
private final HttpClient httpClient;
|
||||
final RequestBuilder builder;
|
||||
final Configuration configuration;
|
||||
final Map<String, String> headers;
|
||||
final Map<String, List<String>> params;
|
||||
private final ClientCredentialsProvider authenticator;
|
||||
private HttpMethodResponse<R> response;
|
||||
|
||||
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder) {
|
||||
public HttpMethod(Configuration configuration, ClientCredentialsProvider authenticator, RequestBuilder builder) {
|
||||
this(configuration, authenticator, builder, new HashMap<String, List<String>>(), new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder, Map<String, List<String>> params, Map<String, String> headers) {
|
||||
public HttpMethod(Configuration configuration, ClientCredentialsProvider authenticator, RequestBuilder builder, Map<String, List<String>> params, Map<String, String> headers) {
|
||||
this.configuration = configuration;
|
||||
this.httpClient = configuration.getHttpClient();
|
||||
this.authenticator = authenticator;
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
package org.keycloak.authorization.client.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
@ -34,16 +39,16 @@ import org.keycloak.representations.idm.authorization.PermissionTicketToken;
|
|||
public class HttpMethodAuthenticator<R> {
|
||||
|
||||
private final HttpMethod<R> method;
|
||||
private final ClientAuthenticator authenticator;
|
||||
private ClientCredentialsProvider clientCredentialProvider;
|
||||
|
||||
public HttpMethodAuthenticator(HttpMethod<R> method, ClientAuthenticator authenticator) {
|
||||
public HttpMethodAuthenticator(HttpMethod<R> method, ClientCredentialsProvider clientCredentialsProvider) {
|
||||
this.method = method;
|
||||
this.authenticator = authenticator;
|
||||
this.clientCredentialProvider = clientCredentialsProvider;
|
||||
}
|
||||
|
||||
public HttpMethod<R> client() {
|
||||
this.method.params.put(OAuth2Constants.GRANT_TYPE, Arrays.asList(OAuth2Constants.CLIENT_CREDENTIALS));
|
||||
authenticator.configureClientCredentials(this.method.params, this.method.headers);
|
||||
configureClientCredentials(this.method.params, this.method.headers);
|
||||
return this.method;
|
||||
}
|
||||
|
||||
|
@ -133,4 +138,12 @@ public class HttpMethodAuthenticator<R> {
|
|||
|
||||
return method;
|
||||
}
|
||||
|
||||
private void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
Map<String, String> formparams = new HashMap<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(method.configuration, clientCredentialProvider, requestHeaders, formparams);
|
||||
for (Entry<String, String> param : formparams.entrySet()) {
|
||||
requestParams.put(param.getKey(), Arrays.asList(param.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
authz/policy-enforcer/pom.xml
Executable file
65
authz/policy-enforcer/pom.xml
Executable file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-parent</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||
<name>Keycloak Authz: Policy Enforcer</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -51,7 +51,7 @@ public class PathCache {
|
|||
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
|
||||
* @param paths the pre-configured paths
|
||||
*/
|
||||
public PathCache(final int maxEntries, long maxAge,
|
||||
PathCache(final int maxEntries, long maxAge,
|
||||
Map<String, PathConfig> paths) {
|
||||
cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
|
||||
@Override
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.common.util.PathMatcher;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PathConfigMatcher extends PathMatcher<PathConfig> {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathCache pathCache;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
PathConfigMatcher(PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
|
||||
this.enforcerConfig = enforcerConfig;
|
||||
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
|
||||
|
||||
if (cacheConfig == null) {
|
||||
cacheConfig = new PathCacheConfig();
|
||||
}
|
||||
|
||||
this.authzClient = authzClient;
|
||||
this.paths = configurePaths();
|
||||
this.pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan(), paths);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configuration:");
|
||||
for (PathConfig pathConfig : this.paths.values()) {
|
||||
LOGGER.debug(pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathConfig matches(String targetUri) {
|
||||
PathConfig pathConfig = pathCache.get(targetUri);
|
||||
|
||||
if (pathCache.containsKey(targetUri) || pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
pathConfig = super.matches(targetUri);
|
||||
|
||||
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
|
||||
if ((pathConfig == null || pathConfig.isInvalidated() || pathConfig.getPath().contains("*"))) {
|
||||
try {
|
||||
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||
|
||||
if (matchingResources.isEmpty()) {
|
||||
// if this config is invalidated (e.g.: due to cache expiration) we remove and return null
|
||||
if (pathConfig != null && pathConfig.isInvalidated()) {
|
||||
paths.remove(targetUri);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Map<String, Map<String, Object>> cipConfig = null;
|
||||
PolicyEnforcerConfig.EnforcementMode enforcementMode = PolicyEnforcerConfig.EnforcementMode.ENFORCING;
|
||||
ResourceRepresentation targetResource = matchingResources.get(0);
|
||||
List<org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig> methodConfig = null;
|
||||
boolean isStatic = false;
|
||||
|
||||
if (pathConfig != null) {
|
||||
cipConfig = pathConfig.getClaimInformationPointConfig();
|
||||
enforcementMode = pathConfig.getEnforcementMode();
|
||||
methodConfig = pathConfig.getMethods();
|
||||
isStatic = pathConfig.isStatic();
|
||||
} else {
|
||||
for (PathConfig existingPath : paths.values()) {
|
||||
if (targetResource.getId().equals(existingPath.getId())
|
||||
&& existingPath.isStatic()
|
||||
&& !org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(existingPath.getEnforcementMode())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathConfig = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
if (cipConfig != null) {
|
||||
pathConfig.setClaimInformationPointConfig(cipConfig);
|
||||
}
|
||||
|
||||
if (methodConfig != null) {
|
||||
pathConfig.setMethods(methodConfig);
|
||||
}
|
||||
|
||||
pathConfig.setStatic(isStatic);
|
||||
pathConfig.setEnforcementMode(enforcementMode);
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCache.put(targetUri, pathConfig);
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPath(PathConfig entry) {
|
||||
return entry.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<PathConfig> getPaths() {
|
||||
return paths.values();
|
||||
}
|
||||
|
||||
public PathCache getPathCache() {
|
||||
return pathCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = authzClient.protection().resource();
|
||||
|
||||
// search by an exact match
|
||||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
// if exact match not found, try to obtain from current path the parent path.
|
||||
// if path is /resource/1/test and pattern from pathConfig is /resource/{id}/*, parent path is /resource/1
|
||||
// this logic allows to match sub resources of a resource instance (/resource/1) to the parent resource,
|
||||
// so any permission granted to parent also applies to sub resources
|
||||
if (search.isEmpty()) {
|
||||
search = resource.findByUri(buildUriFromTemplate(originalConfig.getPath(), path, true));
|
||||
}
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeFromCache(String pathConfig) {
|
||||
pathCache.remove(pathConfig);
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPathConfig() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configurePaths() {
|
||||
ProtectedResource protectedResource = this.authzClient.protection().resource();
|
||||
boolean loadPathsFromServer = !enforcerConfig.getLazyLoadPaths();
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
if (!org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
loadPathsFromServer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
Map<String, PathConfig> paths = configureAllPathsForResourceServer(protectedResource);
|
||||
|
||||
paths.putAll(configureDefinedPaths(protectedResource, enforcerConfig));
|
||||
|
||||
return paths;
|
||||
} else {
|
||||
LOGGER.info("Paths provided in configuration.");
|
||||
return configureDefinedPaths(protectedResource, enforcerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
ResourceRepresentation resource;
|
||||
String resourceName = pathConfig.getName();
|
||||
String path = pathConfig.getPath();
|
||||
|
||||
if (resourceName != null) {
|
||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
||||
resource = protectedResource.findByName(resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
List<ResourceRepresentation> resources = protectedResource.findByUri(path);
|
||||
|
||||
if (resources.isEmpty()) {
|
||||
resources = protectedResource.findByMatchingUri(path);
|
||||
}
|
||||
|
||||
if (resources.size() == 1) {
|
||||
resource = resources.get(0);
|
||||
} else if (resources.size() > 1) {
|
||||
throw new RuntimeException("Multiple resources found with the same uri");
|
||||
} else {
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (resource != null) {
|
||||
pathConfig.setId(resource.getId());
|
||||
// if the resource is statically bound to a resource it means the config can not be invalidated
|
||||
if (resourceName != null) {
|
||||
pathConfig.setStatic(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
pathConfig.setStatic(true);
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths.values()) {
|
||||
if (current.getPath().equals(pathConfig.getPath())) {
|
||||
existingPath = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPath == null) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
} else {
|
||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
if (!enforcerConfig.getLazyLoadPaths()) {
|
||||
for (String id : protectedResource.findAll()) {
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUris() != null && !resourceDescription.getUris().isEmpty()) {
|
||||
for(PathConfig pathConfig : PathConfig.createPathConfigs(resourceDescription)) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,647 @@
|
|||
/*
|
||||
* 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.adapters.authorization;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.JsonUtils.asAccessToken;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.spi.HttpResponse;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessToken.Authorization;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* <p>A Policy Enforcement Point (PEP) that requests and enforces authorization decisions from Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
private static final String HTTP_METHOD_DELETE = "DELETE";
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private final AuthzClient authzClient;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathConfigMatcher pathMatcher;
|
||||
private final HttpClient httpClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
|
||||
|
||||
protected PolicyEnforcer(Builder builder) {
|
||||
enforcerConfig = builder.getEnforcerConfig();
|
||||
authzClient = AuthzClient.create(builder.authzClientConfig);
|
||||
httpClient = authzClient.getConfiguration().getHttpClient();
|
||||
pathMatcher = new PathConfigMatcher(builder.getEnforcerConfig(), authzClient);
|
||||
paths = pathMatcher.getPathConfig();
|
||||
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(HttpRequest request, HttpResponse response) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", request.getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext context = authorize(request, response);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", request.getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||
LOGGER.debugv("Returning authorization context with permissions:");
|
||||
for (Permission permission : context.getPermissions()) {
|
||||
LOGGER.debug(permission);
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPaths() {
|
||||
return Collections.unmodifiableMap(paths);
|
||||
}
|
||||
|
||||
public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
|
||||
return claimInformationPointProviderFactories;
|
||||
}
|
||||
|
||||
public PathConfigMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
private AuthorizationContext authorize(HttpRequest request, HttpResponse response) {
|
||||
EnforcementMode enforcementMode = enforcerConfig.getEnforcementMode();
|
||||
TokenPrincipal principal = request.getPrincipal();
|
||||
boolean anonymous = principal == null || principal.getRawToken() == null;
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
if (anonymous) {
|
||||
response.sendError(401, "Invalid bearer");
|
||||
}
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
PathConfig pathConfig = getPathConfig(request);
|
||||
|
||||
if (anonymous) {
|
||||
if (!isDefaultAccessDeniedUri(request)) {
|
||||
if (pathConfig != null) {
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
} else {
|
||||
challenge(pathConfig, getRequiredScopes(pathConfig, request), request, response);
|
||||
}
|
||||
} else {
|
||||
handleAccessDenied(response);
|
||||
}
|
||||
}
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
AccessToken accessToken = principal.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
}
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
|
||||
}
|
||||
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(response);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, request);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, request, claims)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
AccessToken original = accessToken;
|
||||
|
||||
accessToken = requestAuthorizationToken(pathConfig, methodConfig, request, claims);
|
||||
|
||||
if (accessToken != null) {
|
||||
AccessToken.Authorization authorization = original.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
authorization = new AccessToken.Authorization();
|
||||
authorization.setPermissions(new ArrayList<Permission>());
|
||||
}
|
||||
|
||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
Collection<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
|
||||
for (Permission newPermission : newPermissions) {
|
||||
if (!grantedPermissions.contains(newPermission)) {
|
||||
grantedPermissions.add(newPermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, request, claims)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
}
|
||||
|
||||
if (!challenge(pathConfig, methodConfig, request, response)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||
}
|
||||
handleAccessDenied(response);
|
||||
}
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, HttpRequest request, Map<String, List<String>> claims) {
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Authorization authorization = accessToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasPermission = false;
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : grantedPermissions) {
|
||||
if (permission.getResourceId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||
pathMatcher.removeFromCache(getPath(request));
|
||||
}
|
||||
|
||||
return hasValidClaims(permission, claims);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Map<String, List<String>> resolveClaims(PathConfig pathConfig, HttpRequest request) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
resolveClaims(claims, enforcerConfig.getClaimInformationPointConfig(), request);
|
||||
resolveClaims(claims, pathConfig.getClaimInformationPointConfig(), request);
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, HttpRequest request, HttpResponse response) {
|
||||
if (isBearerAuthorization(request)) {
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, request);
|
||||
|
||||
if (ticket != null) {
|
||||
response.sendError(401);
|
||||
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
|
||||
.append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending challenge");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
handleAccessDenied(response);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleAccessDenied(HttpResponse response) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
response.sendError(302);
|
||||
response.setHeader("Location", accessDeniedPath);
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasValidClaims(Permission permission, Map<String, List<String>> claims) {
|
||||
Map<String, Set<String>> grantedClaims = permission.getClaims();
|
||||
|
||||
if (grantedClaims != null) {
|
||||
if (claims.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Entry<String, Set<String>> entry : grantedClaims.entrySet()) {
|
||||
List<String> requestClaims = claims.get(entry.getKey());
|
||||
|
||||
if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(HttpRequest request) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
||||
List<String> requiredScopes = methodConfig.getScopes();
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
|
||||
if (allowedScopes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig.ScopeEnforcementMode enforcementMode = methodConfig.getScopesEnforcementMode();
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ALL.equals(enforcementMode)) {
|
||||
return allowedScopes.containsAll(requiredScopes);
|
||||
}
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ANY.equals(enforcementMode)) {
|
||||
for (String requiredScope : requiredScopes) {
|
||||
if (allowedScopes.contains(requiredScope)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requiredScopes.isEmpty();
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new ClientAuthorizationContext(authzClient) {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResourcePermission(String resourceName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScopePermission(String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Permission> getPermissions() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
return granted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getPath(HttpRequest request) {
|
||||
return request.getRelativePath();
|
||||
}
|
||||
|
||||
private MethodConfig getRequiredScopes(PathConfig pathConfig, HttpRequest request) {
|
||||
String method = request.getMethod();
|
||||
|
||||
for (MethodConfig methodConfig : pathConfig.getMethods()) {
|
||||
if (methodConfig.getMethod().equals(method)) {
|
||||
return methodConfig;
|
||||
}
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
|
||||
methodConfig.setMethod(request.getMethod());
|
||||
List scopes = new ArrayList<>();
|
||||
|
||||
if (Boolean.TRUE.equals(enforcerConfig.getHttpMethodAsScope())) {
|
||||
scopes.add(request.getMethod());
|
||||
} else {
|
||||
scopes.addAll(pathConfig.getScopes());
|
||||
}
|
||||
|
||||
methodConfig.setScopes(scopes);
|
||||
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
|
||||
|
||||
return methodConfig;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, authzClient);
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
// first we try a match using resource id
|
||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||
|
||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||
}
|
||||
|
||||
return resourceMatch;
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceId().equals(actualPathConfig.getId());
|
||||
}
|
||||
|
||||
private PathConfig getPathConfig(HttpRequest request) {
|
||||
return isDefaultAccessDeniedUri(request) ? null : pathMatcher.matches(getPath(request));
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, HttpRequest request, Map<String, List<String>> claims) {
|
||||
if (enforcerConfig.getUserManagedAccess() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
TokenPrincipal principal = request.getPrincipal();
|
||||
String accessTokenString = principal.getRawToken();
|
||||
AccessToken accessToken = principal.getToken();
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest();
|
||||
|
||||
if (isBearerAuthorization(request) || accessToken.getAuthorization() != null) {
|
||||
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
|
||||
}
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
|
||||
authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
|
||||
}
|
||||
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.setRpt(accessTokenString);
|
||||
}
|
||||
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
AuthorizationResponse authzResponse;
|
||||
|
||||
if (isBearerAuthorization(request)) {
|
||||
authzRequest.setSubjectToken(accessTokenString);
|
||||
authzResponse = authzClient.authorization().authorize(authzRequest);
|
||||
} else {
|
||||
authzResponse = authzClient.authorization(accessTokenString).authorize(authzRequest);
|
||||
}
|
||||
|
||||
if (authzResponse != null) {
|
||||
return asAccessToken(authzResponse.getToken());
|
||||
}
|
||||
} catch (AuthorizationDeniedException ignore) {
|
||||
LOGGER.debug("Authorization denied", ignore);
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Authorization failed", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, MethodConfig methodConfig, AuthzClient authzClient, HttpRequest httpFacade) {
|
||||
if (enforcerConfig.getUserManagedAccess() != null) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
permissionRequest.setClaims(claims);
|
||||
}
|
||||
|
||||
return permission.create(permissionRequest).getTicket();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isBearerAuthorization(HttpRequest request) {
|
||||
List<String> authHeaders = request.getHeaders("Authorization");
|
||||
|
||||
if (authHeaders != null) {
|
||||
for (String authHeader : authHeaders) {
|
||||
String[] split = authHeader.trim().split("\\s+");
|
||||
if (split == null || split.length != 2) continue;
|
||||
if (!split[0].equalsIgnoreCase("Bearer")) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return authzClient.getConfiguration().isBearerOnly();
|
||||
}
|
||||
|
||||
private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
|
||||
for (ClaimInformationPointProviderFactory factory : loader) {
|
||||
factory.init(this);
|
||||
|
||||
claimInformationPointProviderFactories.put(factory.getName(), factory);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveClaims(Map<String, List<String>> claims, Map<String, Map<String, Object>> claimInformationPointConfig, HttpRequest request) {
|
||||
if (claimInformationPointConfig != null) {
|
||||
for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
|
||||
ClaimInformationPointProviderFactory factory = claimInformationPointProviderFactories.get(claimDef.getKey());
|
||||
|
||||
if (factory != null) {
|
||||
claims.putAll(factory.create(claimDef.getValue()).resolve(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
Configuration authzClientConfig = new Configuration();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder authServerUrl(String authServerUrl) {
|
||||
authzClientConfig.setAuthServerUrl(authServerUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder realm(String realm) {
|
||||
authzClientConfig.setRealm(realm);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clientId(String clientId) {
|
||||
authzClientConfig.setResource(clientId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder bearerOnly(boolean bearerOnly) {
|
||||
authzClientConfig.setBearerOnly(bearerOnly);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder credentials(Map<String, Object> credentials) {
|
||||
authzClientConfig.setCredentials(credentials);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enforcerConfig(PolicyEnforcerConfig enforcerConfig) {
|
||||
authzClientConfig.setPolicyEnforcerConfig(enforcerConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enforcerConfig(InputStream is) {
|
||||
try {
|
||||
enforcerConfig(JsonSerialization.readValue(is, PolicyEnforcerConfig.class));
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to read configuration", cause);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder httpClient(HttpClient httpClient) {
|
||||
authzClientConfig.setHttpClient(httpClient);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder credentialProvider(ClientCredentialsProvider credentialsProvider) {
|
||||
authzClientConfig.setClientCredentialsProvider(credentialsProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PolicyEnforcer build() {
|
||||
return new PolicyEnforcer(this);
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return authzClientConfig.getPolicyEnforcerConfig();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.keycloak.adapters.authorization.util.JsonUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
/**
|
||||
* A {@link Principal} backed by a token representing the entity requesting permissions.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface TokenPrincipal extends Principal {
|
||||
|
||||
/**
|
||||
* The token in its raw format.
|
||||
*
|
||||
* @return the token in its raw format.
|
||||
*/
|
||||
String getRawToken();
|
||||
|
||||
/**
|
||||
* The {@link AccessToken} representation of {@link TokenPrincipal#getRawToken()}.
|
||||
*
|
||||
* @return the access token representation
|
||||
*/
|
||||
default AccessToken getToken() {
|
||||
return JsonUtils.asAccessToken(getRawToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the entity represented by the token.
|
||||
*
|
||||
* @return the name of the principal
|
||||
*/
|
||||
default String getName() {
|
||||
return getToken().getPreferredUsername();
|
||||
}
|
||||
}
|
|
@ -23,9 +23,9 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -39,7 +39,7 @@ public class ClaimsInformationPointProvider implements ClaimInformationPointProv
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
public Map<String, List<String>> resolve(HttpRequest request) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
for (Entry<String, Object> configEntry : config.entrySet()) {
|
||||
|
@ -48,11 +48,11 @@ public class ClaimsInformationPointProvider implements ClaimInformationPointProv
|
|||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (claimValue instanceof String) {
|
||||
values = getValues(claimValue.toString(), httpFacade);
|
||||
values = getValues(claimValue.toString(), request);
|
||||
} else if (claimValue instanceof Collection) {
|
||||
|
||||
for (Object value : Collection.class.cast(claimValue)) {
|
||||
List<String> resolvedValues = getValues(value.toString(), httpFacade);
|
||||
List<String> resolvedValues = getValues(value.toString(), request);
|
||||
|
||||
if (!resolvedValues.isEmpty()) {
|
||||
values.addAll(resolvedValues);
|
||||
|
@ -68,7 +68,7 @@ public class ClaimsInformationPointProvider implements ClaimInformationPointProv
|
|||
return claims;
|
||||
}
|
||||
|
||||
private List<String> getValues(String value, HttpFacade httpFacade) {
|
||||
private List<String> getValues(String value, HttpRequest httpFacade) {
|
||||
return PlaceHolders.resolve(value, httpFacade);
|
||||
}
|
||||
}
|
|
@ -18,8 +18,7 @@ package org.keycloak.adapters.authorization.cip;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -31,11 +30,6 @@ public class ClaimsInformationPointProviderFactory implements ClaimInformationPo
|
|||
return "claims";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClaimsInformationPointProvider create(Map<String, Object> config) {
|
||||
return new ClaimsInformationPointProvider(config);
|
|
@ -36,11 +36,10 @@ import org.apache.http.StatusLine;
|
|||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.util.JsonUtils;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -53,15 +52,15 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
private final Map<String, Object> config;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpClaimInformationPointProvider(Map<String, Object> config, PolicyEnforcer policyEnforcer) {
|
||||
public HttpClaimInformationPointProvider(Map<String, Object> config, HttpClient httpClient) {
|
||||
this.config = config;
|
||||
this.httpClient = policyEnforcer.getDeployment().getClient();
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
public Map<String, List<String>> resolve(HttpRequest request) {
|
||||
try {
|
||||
InputStream responseStream = executeRequest(httpFacade);
|
||||
InputStream responseStream = executeRequest(request);
|
||||
|
||||
try (InputStream inputStream = new BufferedInputStream(responseStream)) {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(inputStream);
|
||||
|
@ -102,7 +101,7 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
}
|
||||
}
|
||||
|
||||
private InputStream executeRequest(HttpFacade httpFacade) {
|
||||
private InputStream executeRequest(HttpRequest request) {
|
||||
String method = config.get("method").toString();
|
||||
|
||||
if (method == null) {
|
||||
|
@ -122,10 +121,10 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
byte[] bytes = new byte[0];
|
||||
|
||||
try {
|
||||
setParameters(builder, httpFacade);
|
||||
setParameters(builder, request);
|
||||
|
||||
if (config.containsKey("headers")) {
|
||||
setHeaders(builder, httpFacade);
|
||||
setHeaders(builder, request);
|
||||
}
|
||||
|
||||
HttpResponse response = httpClient.execute(builder.build());
|
||||
|
@ -152,7 +151,7 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
}
|
||||
}
|
||||
|
||||
private void setHeaders(RequestBuilder builder, HttpFacade httpFacade) {
|
||||
private void setHeaders(RequestBuilder builder, HttpRequest request) {
|
||||
Object headersDef = config.get("headers");
|
||||
|
||||
if (headersDef != null) {
|
||||
|
@ -166,10 +165,10 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
Collection values = Collection.class.cast(value);
|
||||
|
||||
for (Object item : values) {
|
||||
headerValues.addAll(PlaceHolders.resolve(item.toString(), httpFacade));
|
||||
headerValues.addAll(PlaceHolders.resolve(item.toString(), request));
|
||||
}
|
||||
} else {
|
||||
headerValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
|
||||
headerValues.addAll(PlaceHolders.resolve(value.toString(), request));
|
||||
}
|
||||
|
||||
for (String headerValue : headerValues) {
|
||||
|
@ -179,7 +178,7 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
}
|
||||
}
|
||||
|
||||
private void setParameters(RequestBuilder builder, HttpFacade httpFacade) {
|
||||
private void setParameters(RequestBuilder builder, HttpRequest request) {
|
||||
Object config = this.config.get("parameters");
|
||||
|
||||
if (config != null) {
|
||||
|
@ -193,10 +192,10 @@ public class HttpClaimInformationPointProvider implements ClaimInformationPointP
|
|||
Collection values = Collection.class.cast(value);
|
||||
|
||||
for (Object item : values) {
|
||||
paramValues.addAll(PlaceHolders.resolve(item.toString(), httpFacade));
|
||||
paramValues.addAll(PlaceHolders.resolve(item.toString(), request));
|
||||
}
|
||||
} else {
|
||||
paramValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
|
||||
paramValues.addAll(PlaceHolders.resolve(value.toString(), request));
|
||||
}
|
||||
|
||||
for (String paramValue : paramValues) {
|
|
@ -18,8 +18,8 @@ package org.keycloak.adapters.authorization.cip;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -40,6 +40,6 @@ public class HttpClaimInformationPointProviderFactory implements ClaimInformatio
|
|||
|
||||
@Override
|
||||
public HttpClaimInformationPointProvider create(Map<String, Object> config) {
|
||||
return new HttpClaimInformationPointProvider(config, policyEnforcer);
|
||||
return new HttpClaimInformationPointProvider(config, policyEnforcer.getHttpClient());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2023 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");
|
||||
|
@ -14,17 +14,17 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
package org.keycloak.adapters.authorization.cip.spi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProvider {
|
||||
|
||||
Map<String, List<String>> resolve(HttpFacade httpFacade);
|
||||
Map<String, List<String>> resolve(HttpRequest request);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2023 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");
|
||||
|
@ -14,11 +14,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
package org.keycloak.adapters.authorization.cip.spi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -27,7 +27,9 @@ public interface ClaimInformationPointProviderFactory<C extends ClaimInformation
|
|||
|
||||
String getName();
|
||||
|
||||
void init(PolicyEnforcer policyEnforcer);
|
||||
default void init(PolicyEnforcer policyEnforcer) {
|
||||
|
||||
}
|
||||
|
||||
C create(Map<String, Object> config);
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.spi;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
|
||||
/**
|
||||
* Represents an incoming HTTP request and the contract to manipulate it.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface HttpRequest {
|
||||
|
||||
/**
|
||||
* Get the request path. This is the path relative to the context path.
|
||||
* E.g.: for a HTTP GET request to http://my.appserver.com/my-application/path/sub-path this method is going to return /path/sub-path.
|
||||
|
||||
* @return the relative path
|
||||
*/
|
||||
String getRelativePath();
|
||||
|
||||
/**
|
||||
* Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
|
||||
*
|
||||
* @return a {@code String} specifying the name of the method with which this request was made
|
||||
*/
|
||||
String getMethod();
|
||||
|
||||
/**
|
||||
* Get the URI representation for the current request.
|
||||
*
|
||||
* @return a {@code String} representation for the current request
|
||||
*/
|
||||
String getURI();
|
||||
|
||||
/**
|
||||
* Get a list of all of the values set for the specified header within the HTTP request.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return a list of the values set for this header, if the header is not set on the request then null should be returned
|
||||
*/
|
||||
List<String> getHeaders(String name);
|
||||
|
||||
/**
|
||||
* Get the first value for a parameter with the given {@code name}
|
||||
*
|
||||
* @param name the parameter name
|
||||
* @return the value of the parameter
|
||||
*/
|
||||
String getFirstParam(String name);
|
||||
|
||||
/**
|
||||
* Get the first value for a cookie with the given {@code name}.
|
||||
*
|
||||
* @param name the parameter name
|
||||
* @return the value of the cookie
|
||||
*/
|
||||
String getCookieValue(String name);
|
||||
|
||||
/**
|
||||
* Returns the client address.
|
||||
*
|
||||
* @return the client address.
|
||||
*/
|
||||
String getRemoteAddr();
|
||||
|
||||
/**
|
||||
* Indicates if the request is coming from a secure channel through HTTPS.
|
||||
*
|
||||
* @return {@code true} if the HTTP scheme is set to 'https'. Otherwise, {@code false}
|
||||
*/
|
||||
boolean isSecure();
|
||||
|
||||
/**
|
||||
* Get the first value for a HEADER with the given {@code name}.
|
||||
*
|
||||
* @param name the HEADER name
|
||||
* @return the value of the HEADER
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
/**
|
||||
* Returns the request input stream
|
||||
*
|
||||
* @param buffered if the input stream should be buffered and support for multiple reads
|
||||
* @return the request input stream
|
||||
*/
|
||||
InputStream getInputStream(boolean buffered);
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenPrincipal} associated with the request.
|
||||
*
|
||||
* @return the principal
|
||||
*/
|
||||
TokenPrincipal getPrincipal();
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.spi;
|
||||
|
||||
/**
|
||||
* Represents an outgoing HTTP response and the contract to manipulate it.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface HttpResponse {
|
||||
|
||||
/**
|
||||
* Send an error with the given {@code statusCode}.
|
||||
*
|
||||
* @param statusCode the status to set in the response
|
||||
*/
|
||||
void sendError(int statusCode);
|
||||
|
||||
/**
|
||||
* Send an error with the given {@code statusCode} and {@code reason} message.
|
||||
*
|
||||
* @param statusCode the status to set in the response
|
||||
*/
|
||||
void sendError(int statusCode, String reason);
|
||||
|
||||
/**
|
||||
* Set a header with the given {@code name} and {@code value}.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
void setHeader(String name, String value);
|
||||
}
|
|
@ -21,9 +21,13 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* Utility methods to manipulate JSON data
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
@ -36,7 +40,6 @@ public class JsonUtils {
|
|||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (jsonNode.isArray()) {
|
||||
|
||||
for (JsonNode node : jsonNode) {
|
||||
String value;
|
||||
|
||||
|
@ -65,4 +68,11 @@ public class JsonUtils {
|
|||
return values;
|
||||
}
|
||||
|
||||
public static AccessToken asAccessToken(String rawToken) {
|
||||
try {
|
||||
return new JWSInput(rawToken).readJsonContent(AccessToken.class);
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to decode token", cause);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,9 +22,8 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
|
@ -35,29 +34,18 @@ public class KeycloakSecurityContextPlaceHolderResolver implements PlaceHolderRe
|
|||
public static final String NAME = "keycloak";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
|
||||
public List<String> resolve(String placeHolder, HttpRequest request) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
OIDCHttpFacade oidcHttpFacade = OIDCHttpFacade.class.cast(httpFacade);
|
||||
KeycloakSecurityContext securityContext = oidcHttpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
TokenPrincipal principal = request.getPrincipal();
|
||||
|
||||
if (source.endsWith("access_token")) {
|
||||
return Arrays.asList(securityContext.getTokenString());
|
||||
}
|
||||
|
||||
if (source.endsWith("id_token")) {
|
||||
return Arrays.asList(securityContext.getIdTokenString());
|
||||
return Arrays.asList(principal.getRawToken());
|
||||
}
|
||||
|
||||
JsonNode jsonNode;
|
||||
|
||||
if (source.startsWith("access_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getToken());
|
||||
} else if (source.startsWith("id_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getIdToken());
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(principal.getToken());
|
||||
} else {
|
||||
throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
|
||||
}
|
|
@ -18,13 +18,13 @@ package org.keycloak.adapters.authorization.util;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface PlaceHolderResolver {
|
||||
|
||||
List<String> resolve(String placeHolder, HttpFacade httpFacade);
|
||||
List<String> resolve(String placeHolder, HttpRequest httpFacade);
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.adapters.authorization.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -24,7 +25,7 @@ import java.util.Map.Entry;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -41,7 +42,7 @@ public class PlaceHolders {
|
|||
private static Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(.+?)\\}");
|
||||
private static Pattern PLACEHOLDER_PARAM_PATTERN = Pattern.compile("\\[(.+?)\\]");
|
||||
|
||||
public static List<String> resolve(String value, HttpFacade httpFacade) {
|
||||
public static List<String> resolve(String value, HttpRequest httpFacade) {
|
||||
Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);
|
||||
|
||||
if (!placeHolders.isEmpty()) {
|
||||
|
@ -75,27 +76,31 @@ public class PlaceHolders {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static Map<String, List<String>> parsePlaceHolders(String value, HttpFacade httpFacade) {
|
||||
Map<String, List<String>> placeHolders = new HashMap<>();
|
||||
private static Map<String, List<String>> parsePlaceHolders(String value, HttpRequest httpFacade) {
|
||||
Map<String, List<String>> placeHolders = Collections.emptyMap();
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
|
||||
boolean found = matcher.find();
|
||||
|
||||
while (matcher.find()) {
|
||||
String placeHolder = matcher.group(1);
|
||||
int resolverNameIdx = placeHolder.indexOf('.');
|
||||
if (found) {
|
||||
placeHolders = new HashMap<>();
|
||||
do {
|
||||
String placeHolder = matcher.group(1);
|
||||
int resolverNameIdx = placeHolder.indexOf('.');
|
||||
|
||||
if (resolverNameIdx == -1) {
|
||||
throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
|
||||
}
|
||||
|
||||
PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
|
||||
|
||||
if (resolver != null) {
|
||||
List<String> resolved = resolver.resolve(placeHolder, httpFacade);
|
||||
|
||||
if (resolved != null) {
|
||||
placeHolders.put(formatPlaceHolder(placeHolder), resolved);
|
||||
if (resolverNameIdx == -1) {
|
||||
throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
|
||||
}
|
||||
}
|
||||
|
||||
PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
|
||||
|
||||
if (resolver != null) {
|
||||
List<String> resolved = resolver.resolve(placeHolder, httpFacade);
|
||||
|
||||
if (resolved != null) {
|
||||
placeHolders.put(formatPlaceHolder(placeHolder), resolved);
|
||||
}
|
||||
}
|
||||
} while (matcher.find());
|
||||
}
|
||||
|
||||
return placeHolders;
|
|
@ -29,9 +29,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
|
@ -42,17 +40,12 @@ public class RequestPlaceHolderResolver implements PlaceHolderResolver {
|
|||
static String NAME = "request";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
|
||||
public List<String> resolve(String placeHolder, HttpRequest request) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
Request request = httpFacade.getRequest();
|
||||
|
||||
if (source.startsWith("parameter")) {
|
||||
String parameterName = getParameter(source, "Could not obtain parameter name from placeholder [" + source + "]");
|
||||
String parameterValue = request.getQueryParamValue(parameterName);
|
||||
|
||||
if (parameterValue == null) {
|
||||
parameterValue = request.getFirstParam(parameterName);
|
||||
}
|
||||
String parameterValue = request.getFirstParam(parameterName);
|
||||
|
||||
if (parameterValue != null) {
|
||||
return Arrays.asList(parameterValue);
|
||||
|
@ -66,10 +59,10 @@ public class RequestPlaceHolderResolver implements PlaceHolderResolver {
|
|||
}
|
||||
} else if (source.startsWith("cookie")) {
|
||||
String cookieName = getParameter(source, "Could not obtain cookie name from placeholder [" + source + "]");
|
||||
Cookie cookieValue = request.getCookie(cookieName);
|
||||
String cookieValue = request.getCookieValue(cookieName);
|
||||
|
||||
if (cookieValue != null) {
|
||||
return Arrays.asList(cookieValue.getValue());
|
||||
return Arrays.asList(cookieValue);
|
||||
}
|
||||
} else if (source.startsWith("remoteAddr")) {
|
||||
String value = request.getRemoteAddr();
|
|
@ -32,7 +32,7 @@
|
|||
<artifactId>keycloak-authz-policy-common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>KeyCloak AuthZ: Common Policy Providers</name>
|
||||
<name>Keycloak AuthZ: Common Policy Providers</name>
|
||||
<description>KeyCloak AuthZ: Common Policy Providers</description>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
<artifactId>keycloak-authz-provider-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>KeyCloak AuthZ: Provider Parent</name>
|
||||
<description>KeyCloak AuthZ: Provider Parent</description>
|
||||
<name>Keycloak AuthZ: Policy Provider Parent</name>
|
||||
|
||||
<modules>
|
||||
<module>common</module>
|
||||
|
|
|
@ -14,11 +14,12 @@
|
|||
<artifactId>keycloak-authz-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>KeyCloak Authz: Parent</name>
|
||||
<name>Keycloak Authz: Parent</name>
|
||||
<description>KeyCloak AuthZ: Parent</description>
|
||||
|
||||
<modules>
|
||||
<module>policy</module>
|
||||
<module>client</module>
|
||||
<module>policy-enforcer</module>
|
||||
</modules>
|
||||
</project>
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2023 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");
|
||||
|
@ -15,12 +15,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
package org.keycloak.protocol.oidc.client.authentication;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
/**
|
||||
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
|
||||
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
|
||||
|
@ -30,15 +30,12 @@ import java.util.Map;
|
|||
* so your server is able to authenticate client
|
||||
*
|
||||
* You must specify a file
|
||||
* META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
|
||||
* META-INF/services/org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
|
||||
* if you want to share the implementation among more WARs).
|
||||
*
|
||||
* NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support
|
||||
* authentication with client certificate)
|
||||
*
|
||||
* @see ClientIdAndSecretCredentialsProvider
|
||||
* @see JWTClientCredentialsProvider
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface ClientCredentialsProvider {
|
||||
|
@ -62,17 +59,17 @@ public interface ClientCredentialsProvider {
|
|||
/**
|
||||
* Called by adapter during deployment of your application. You can for example read configuration and init your authenticator here
|
||||
*
|
||||
* @param deployment the adapter configuration
|
||||
* @param adapterConfig the adapter configuration
|
||||
* @param config the configuration of your provider read from keycloak.json . For the kerberos-keytab example above, it will return map with the single key "keytab" with value "/tmp/foo"
|
||||
*/
|
||||
void init(KeycloakDeployment deployment, Object config);
|
||||
void init(AdapterConfig adapterConfig, Object config);
|
||||
|
||||
/**
|
||||
* Called every time adapter needs to perform backchannel request
|
||||
*
|
||||
* @param deployment Fully resolved deployment
|
||||
* @param adapterConfig Fully resolved deployment
|
||||
* @param requestHeaders You should put any HTTP request headers you want to use for authentication of client. These headers will be attached to the HTTP request sent to Keycloak server
|
||||
* @param formParams You should put any request parameters you want to use for authentication of client. These parameters will be attached to the HTTP request sent to Keycloak server
|
||||
*/
|
||||
void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams);
|
||||
void setClientCredentials(AdapterConfig adapterConfig, Map<String, String> requestHeaders, Map<String, String> formParams);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2023 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");
|
||||
|
@ -15,21 +15,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
package org.keycloak.protocol.oidc.client.authentication;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
@ -37,9 +33,9 @@ public class ClientCredentialsProviderUtils {
|
|||
|
||||
private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
|
||||
|
||||
public static ClientCredentialsProvider bootstrapClientAuthenticator(KeycloakDeployment deployment) {
|
||||
String clientId = deployment.getResourceName();
|
||||
Map<String, Object> clientCredentials = deployment.getResourceCredentials();
|
||||
public static ClientCredentialsProvider bootstrapClientAuthenticator(AdapterConfig deployment) {
|
||||
String clientId = deployment.getResource();
|
||||
Map<String, Object> clientCredentials = deployment.getCredentials();
|
||||
|
||||
String authenticatorId;
|
||||
if (clientCredentials == null || clientCredentials.isEmpty()) {
|
||||
|
@ -73,7 +69,7 @@ public class ClientCredentialsProviderUtils {
|
|||
return authenticator;
|
||||
}
|
||||
|
||||
private static void loadAuthenticators(Map<String, ClientCredentialsProvider> authenticators, ClassLoader classLoader) {
|
||||
public static void loadAuthenticators(Map<String, ClientCredentialsProvider> authenticators, ClassLoader classLoader) {
|
||||
Iterator<ClientCredentialsProvider> iterator = ServiceLoader.load(ClientCredentialsProvider.class, classLoader).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
try {
|
||||
|
@ -91,26 +87,7 @@ public class ClientCredentialsProviderUtils {
|
|||
/**
|
||||
* Use this method when calling backchannel request directly from your application. See service-account example from demo for more details
|
||||
*/
|
||||
public static void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formparams) {
|
||||
ClientCredentialsProvider authenticator = deployment.getClientAuthenticator();
|
||||
public static void setClientCredentials(AdapterConfig deployment, ClientCredentialsProvider authenticator, Map<String, String> requestHeaders, Map<String, String> formparams) {
|
||||
authenticator.setClientCredentials(deployment, requestHeaders, formparams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use directly from your JEE apps to avoid HttpClient linkage errors! Instead use the method {@link #setClientCredentials(KeycloakDeployment, Map, Map)}
|
||||
*/
|
||||
public static void setClientCredentials(KeycloakDeployment deployment, HttpPost post, List<NameValuePair> formparams) {
|
||||
Map<String, String> reqHeaders = new HashMap<>();
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
setClientCredentials(deployment, reqHeaders, reqParams);
|
||||
|
||||
for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
|
||||
post.setHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> param : reqParams.entrySet()) {
|
||||
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,16 +15,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
package org.keycloak.protocol.oidc.client.authentication;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Traditional OAuth2 authentication of clients based on client_id and client_secret
|
||||
*
|
||||
|
@ -44,13 +44,13 @@ public class ClientIdAndSecretCredentialsProvider implements ClientCredentialsPr
|
|||
}
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
public void init(AdapterConfig deployment, Object config) {
|
||||
clientSecret = (config == null ? null : config.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String clientId = deployment.getResourceName();
|
||||
public void setClientCredentials(AdapterConfig deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String clientId = deployment.getResource();
|
||||
|
||||
if (!deployment.isPublicClient()) {
|
||||
if (clientSecret != null) {
|
|
@ -15,18 +15,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
package org.keycloak.protocol.oidc.client.authentication;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
|
@ -36,6 +33,9 @@ import org.keycloak.crypto.KeyType;
|
|||
import org.keycloak.crypto.KeyUse;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.crypto.SignatureSignerContext;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
|
@ -102,16 +102,16 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
public void init(AdapterConfig deployment, Object config) {
|
||||
if (!(config instanceof Map)) {
|
||||
throw new RuntimeException("Configuration of jwt credentials is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
|
||||
throw new RuntimeException("Configuration of jwt credentials is missing or incorrect for client '" + deployment.getResource() + "'. Check your adapter configuration");
|
||||
}
|
||||
|
||||
Map<String, Object> cfg = (Map<String, Object>) config;
|
||||
|
||||
String clientKeystoreFile = (String) cfg.get("client-keystore-file");
|
||||
if (clientKeystoreFile == null) {
|
||||
throw new RuntimeException("Missing parameter client-keystore-file in configuration of jwt for client " + deployment.getResourceName());
|
||||
throw new RuntimeException("Missing parameter client-keystore-file in configuration of jwt for client " + deployment.getResource());
|
||||
}
|
||||
|
||||
String clientKeystoreType = (String) cfg.get("client-keystore-type");
|
||||
|
@ -119,7 +119,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||
|
||||
String clientKeystorePassword = (String) cfg.get("client-keystore-password");
|
||||
if (clientKeystorePassword == null) {
|
||||
throw new RuntimeException("Missing parameter client-keystore-password in configuration of jwt for client " + deployment.getResourceName());
|
||||
throw new RuntimeException("Missing parameter client-keystore-password in configuration of jwt for client " + deployment.getResource());
|
||||
}
|
||||
|
||||
String clientKeyPassword = (String) cfg.get("client-key-password");
|
||||
|
@ -129,7 +129,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||
|
||||
String clientKeyAlias = (String) cfg.get("client-key-alias");
|
||||
if (clientKeyAlias == null) {
|
||||
clientKeyAlias = deployment.getResourceName();
|
||||
clientKeyAlias = deployment.getResource();
|
||||
}
|
||||
|
||||
String algorithm = (String) cfg.getOrDefault("algorithm", Algorithm.RS256);
|
||||
|
@ -157,8 +157,8 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
|
||||
public void setClientCredentials(AdapterConfig deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResource(), deployment.getRealmInfoUrl());
|
||||
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
|
||||
|
@ -173,7 +173,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||
|
||||
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||
JsonWebToken reqToken = new JsonWebToken();
|
||||
reqToken.id(AdapterUtils.generateId());
|
||||
reqToken.id(UUID.randomUUID().toString());
|
||||
reqToken.issuer(clientId);
|
||||
reqToken.subject(clientId);
|
||||
reqToken.audience(realmInfoUrl);
|
|
@ -14,23 +14,23 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authentication;
|
||||
package org.keycloak.protocol.oidc.client.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.JavaAlgorithm;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client secret instead of private key .
|
||||
|
@ -53,15 +53,15 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
|||
}
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
public void init(AdapterConfig deployment, Object config) {
|
||||
if (!(config instanceof Map)) {
|
||||
throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
|
||||
throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResource() + "'. Check your adapter configuration");
|
||||
}
|
||||
|
||||
Map<String, Object> cfg = (Map<String, Object>) config;
|
||||
String clientSecretString = (String) cfg.get("secret");
|
||||
if (clientSecretString == null) {
|
||||
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResource());
|
||||
}
|
||||
|
||||
String clientSecretJwtAlg = (String) cfg.get("algorithm");
|
||||
|
@ -72,7 +72,7 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
|||
setClientSecret(clientSecretString, clientSecretJwtAlg);
|
||||
} else {
|
||||
// invalid "algorithm" field
|
||||
throw new RuntimeException("Invalid parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||
throw new RuntimeException("Invalid parameter secret-jwt in configuration of jwt for client " + deployment.getResource());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,8 +84,8 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
|
||||
public void setClientCredentials(AdapterConfig deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResource(), deployment.getRealmInfoUrl());
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
|||
// JWT claims is the same as one by private_key_jwt
|
||||
|
||||
JsonWebToken reqToken = new JsonWebToken();
|
||||
reqToken.id(AdapterUtils.generateId());
|
||||
reqToken.id(UUID.randomUUID().toString());
|
||||
reqToken.issuer(clientId);
|
||||
reqToken.subject(clientId);
|
||||
reqToken.audience(realmInfoUrl);
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.representations.adapters.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
|
||||
|
@ -320,4 +321,9 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
|
|||
public void setConnectionTTL(long connectionTTL) {
|
||||
this.connectionTTL = connectionTTL;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getRealmInfoUrl() {
|
||||
return authServerUrl + "/realms/" + realm;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
|
||||
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
|
||||
org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider
|
||||
org.keycloak.protocol.oidc.client.authentication.ClientIdAndSecretCredentialsProvider
|
||||
org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider
|
||||
org.keycloak.protocol.oidc.client.authentication.JWTClientSecretCredentialsProvider
|
|
@ -154,6 +154,16 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.wildfly</groupId>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<module name="org.keycloak.keycloak-core"/>
|
||||
<module name="org.keycloak.keycloak-crypto-default" services="import"/>
|
||||
<module name="org.keycloak.keycloak-authz-client"/>
|
||||
<module name="org.keycloak.keycloak-policy-enforcer"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-policy-enforcer">
|
||||
<resources>
|
||||
<artifact name="${org.keycloak:keycloak-policy-enforcer}"/>
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="org.bouncycastle" />
|
||||
<module name="javax.api"/>
|
||||
<module name="javax.activation.api"/>
|
||||
<module name="sun.jdk" optional="true" />
|
||||
<module name="javax.ws.rs.api"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
<module name="org.keycloak.keycloak-common"/>
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-annotations"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
||||
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
|
||||
<module name="org.jboss.logging"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
5
pom.xml
5
pom.xml
|
@ -1443,6 +1443,11 @@
|
|||
<artifactId>keycloak-authz-policy-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Quarkus -->
|
||||
<dependency>
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.keycloak.provider.Provider;
|
|||
* This interface is for users that want to add custom client authenticators to an authentication flow.
|
||||
* You must implement this interface as well as a ClientAuthenticatorFactory.
|
||||
*
|
||||
* This interface is for verifying client credentials from request. On the adapter side, you must also implement org.keycloak.adapters.authentication.ClientCredentialsProvider , which is supposed
|
||||
* This interface is for verifying client credentials from request. On the adapter side, you must also implement org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider , which is supposed
|
||||
* to add the client credentials to the request, which will ClientAuthenticator verify on server side
|
||||
*
|
||||
* @see org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
|
|
|
@ -25,18 +25,13 @@ import static org.junit.Assert.fail;
|
|||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
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.ClientResource;
|
||||
|
@ -44,7 +39,6 @@ import org.keycloak.admin.client.resource.ClientsResource;
|
|||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
|
@ -356,16 +350,7 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
|
|||
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(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
Map<String, String> formparams = new HashMap<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, requestHeaders, formparams);
|
||||
for (Entry<String, String> param : formparams.entrySet()) {
|
||||
requestParams.put(param.getKey(), Arrays.asList(param.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
return AuthzClient.create(new Configuration(deployment.getAuthServerBaseUrl(), deployment.getRealm(), deployment.getResourceName(), deployment.getResourceCredentials(), deployment.getClient()));
|
||||
}
|
||||
|
||||
private InputStream getConfigurationStream(String adapterConfig) {
|
||||
|
|
|
@ -46,14 +46,14 @@ import org.junit.AfterClass;
|
|||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.spi.AuthenticationError;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
||||
|
@ -105,7 +105,7 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
exchange.setStatusCode(200);
|
||||
} else if (exchange.getRelativePath().equals("/get-claim-information-provider")) {
|
||||
if (!"Bearer idTokenString".equals(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||
if (!"Bearer tokenString".equals(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||
|| !"get".equalsIgnoreCase(exchange.getRequestMethod().toString())
|
||||
|| !exchange.getRequestHeaders().get("header-b").contains("header-b-value1")
|
||||
|| !exchange.getRequestHeaders().get("header-b").contains("header-b-value2")
|
||||
|
@ -178,8 +178,8 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testBasicClaimsInformationPoint() {
|
||||
HttpFacade httpFacade = createHttpFacade();
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);
|
||||
OIDCHttpFacade httpFacade = createHttpFacade();
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals("parameter-a", claims.get("claim-from-request-parameter").get(0));
|
||||
assertEquals("header-b", claims.get("claim-from-header").get(0));
|
||||
|
@ -204,9 +204,9 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
ObjectMapper mapper = JsonSerialization.mapper;
|
||||
JsonParser parser = mapper.getFactory().createParser("{\"a\": {\"b\": {\"c\": \"c-value\"}}, \"d\": [\"d-value1\", \"d-value2\"], \"e\": {\"number\": 123}}");
|
||||
TreeNode treeNode = mapper.readTree(parser);
|
||||
HttpFacade httpFacade = createHttpFacade(headers, new ByteArrayInputStream(treeNode.toString().getBytes()));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade(headers, new ByteArrayInputStream(treeNode.toString().getBytes()));
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals("c-value", claims.get("claim-from-json-body-object").get(0));
|
||||
assertEquals("d-value2", claims.get("claim-from-json-body-array").get(0));
|
||||
|
@ -244,9 +244,9 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
+ "\n"
|
||||
+ "}}");
|
||||
TreeNode treeNode = mapper.readTree(parser);
|
||||
HttpFacade httpFacade = createHttpFacade(headers, new ByteArrayInputStream(treeNode.toString().getBytes()));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade(headers, new ByteArrayInputStream(treeNode.toString().getBytes()));
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-from-body-json-object", "claims").resolve(httpFacade);
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-from-body-json-object", "claims").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals(1, claims.size());
|
||||
assertEquals(2, claims.get("individualRoles").size());
|
||||
|
@ -256,7 +256,7 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
headers.put("Content-Type", Arrays.asList("application/json; charset=utf-8"));
|
||||
|
||||
httpFacade = createHttpFacade(headers, new ByteArrayInputStream(treeNode.toString().getBytes()));
|
||||
claims = getClaimInformationProviderForPath("/claims-from-body-json-object", "claims").resolve(httpFacade);
|
||||
claims = getClaimInformationProviderForPath("/claims-from-body-json-object", "claims").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals(1, claims.size());
|
||||
assertEquals(2, claims.get("individualRoles").size());
|
||||
|
@ -266,18 +266,18 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testBodyClaimsInformationPoint() {
|
||||
HttpFacade httpFacade = createHttpFacade(new HashMap<>(), new ByteArrayInputStream("raw-body-text".getBytes()));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade(new HashMap<>(), new ByteArrayInputStream("raw-body-text".getBytes()));
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals("raw-body-text", claims.get("claim-from-body").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpClaimInformationPointProviderWithoutClaims() {
|
||||
HttpFacade httpFacade = createHttpFacade();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade();
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-get-claim-provider", "http").resolve(httpFacade);
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-get-claim-provider", "http").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals("a-value1", claims.get("a").get(0));
|
||||
assertEquals("b-value1", claims.get("b").get(0));
|
||||
|
@ -292,9 +292,9 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testHttpClaimInformationPointProviderWithClaims() {
|
||||
HttpFacade httpFacade = createHttpFacade();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade();
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-post-claim-provider", "http").resolve(httpFacade);
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-post-claim-provider", "http").resolve(new HttpAuthzRequest(httpFacade));
|
||||
|
||||
assertEquals("a-value1", claims.get("claim-a").get(0));
|
||||
assertEquals("d-value1", claims.get("claim-d").get(0));
|
||||
|
@ -308,7 +308,7 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
assertNull(claims.get("d"));
|
||||
}
|
||||
|
||||
private HttpFacade createHttpFacade(Map<String, List<String>> headers, InputStream requestBody) {
|
||||
private OIDCHttpFacade createHttpFacade(Map<String, List<String>> headers, InputStream requestBody) {
|
||||
return new OIDCHttpFacade() {
|
||||
private Request request;
|
||||
|
||||
|
@ -349,7 +349,7 @@ public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
|||
};
|
||||
}
|
||||
|
||||
private HttpFacade createHttpFacade() {
|
||||
private OIDCHttpFacade createHttpFacade() {
|
||||
return createHttpFacade(new HashMap<>(), null);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,9 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
public class MyCustomCIPFactory implements ClaimInformationPointProviderFactory<MyCustomCIP> {
|
||||
|
||||
|
@ -33,11 +32,6 @@ public class MyCustomCIPFactory implements ClaimInformationPointProviderFactory<
|
|||
return "my-custom-cip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public MyCustomCIP create(Map<String, Object> config) {
|
||||
return new MyCustomCIP(config);
|
||||
|
@ -53,7 +47,7 @@ class MyCustomCIP implements ClaimInformationPointProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
public Map<String, List<String>> resolve(HttpRequest request) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
claims.put("resolved-claim", Arrays.asList(config.get("claim-value").toString()));
|
||||
|
|
|
@ -41,6 +41,8 @@ import org.junit.Test;
|
|||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
||||
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
|
@ -132,7 +134,8 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
|
||||
headers.put("Authorization", Arrays.asList("Bearer " + token));
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
|
||||
OIDCHttpFacade facade = createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
AuthorizationRequest request = new AuthorizationRequest();
|
||||
|
@ -144,22 +147,26 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
|
||||
assertNotNull(token);
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
|
||||
request = new AuthorizationRequest();
|
||||
|
||||
|
@ -168,7 +175,8 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
response = authzClient.authorization("marta", "password").authorize(request);
|
||||
token = response.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
request = new AuthorizationRequest();
|
||||
|
@ -178,7 +186,8 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
response = authzClient.authorization("marta", "password").authorize(request);
|
||||
token = response.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "GET", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", "GET", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
assertEquals(1, context.getPermissions().size());
|
||||
|
@ -199,12 +208,14 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
|
||||
String token = authzClient.obtainAccessToken("marta", "password").getToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
OIDCHttpFacade facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
assertEquals(1, context.getPermissions().size());
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
|
@ -212,17 +223,20 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
|
@ -245,27 +259,32 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
|
||||
headers.put("Authorization", Arrays.asList("Bearer " + token));
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
OIDCHttpFacade facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
@ -289,27 +308,32 @@ public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
|||
|
||||
headers.put("Authorization", Arrays.asList("Bearer " + token));
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
OIDCHttpFacade facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
|
||||
facade = createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(facade), new HttpAuthzResponse(facade));
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ import org.keycloak.KeycloakSecurityContext;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.CorsHeaders;
|
||||
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
||||
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
|
@ -144,7 +146,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-bearer-only.json"));
|
||||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resourcea");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
|
||||
|
@ -159,12 +161,12 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
httpFacade = createHttpFacade("/api/resourcea", token);
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resourceb");
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
|
||||
}
|
||||
|
@ -174,7 +176,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-paths.json"));
|
||||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resourcea");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
|
||||
|
@ -189,12 +191,12 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
httpFacade = createHttpFacade("/api/resourcea", token);
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/");
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -221,7 +223,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
}
|
||||
});
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
Map<String, Set<String>> claims = permission.getClaims();
|
||||
|
||||
|
@ -245,7 +247,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resourcea", token);
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
Map<String, Set<String>> claims = permission.getClaims();
|
||||
|
||||
|
@ -258,7 +260,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-on-deny-redirect.json"));
|
||||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resourcea");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
TestResponse response = TestResponse.class.cast(httpFacade.getResponse());
|
||||
|
@ -273,7 +275,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-bearer-only.json"));
|
||||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/unmmaped");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
TestResponse response = TestResponse.class.cast(httpFacade.getResponse());
|
||||
|
@ -307,11 +309,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resource/public");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resourceb");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
TestResponse response = TestResponse.class.cast(httpFacade.getResponse());
|
||||
assertEquals(403, response.getStatus());
|
||||
|
@ -322,17 +324,17 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), null).getAccessToken();
|
||||
|
||||
httpFacade = createHttpFacade("/api/resourcea", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resourceb", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
response = TestResponse.class.cast(httpFacade.getResponse());
|
||||
assertEquals(403, response.getStatus());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource/public", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -342,7 +344,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resource/public");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
|
@ -353,14 +355,14 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
// first request caches the path and the entry is invalidated due to the lifespan
|
||||
httpFacade = createHttpFacade("/api/resource/all-public");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
WaitUtils.pause(1000);
|
||||
|
||||
// second request can not fail because entry should not be invalidated
|
||||
httpFacade = createHttpFacade("/api/resource/all-public");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -389,11 +391,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
String token = response.getAccessToken();
|
||||
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/any-resource/test", token);
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/any-resource/test", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
ResourceRepresentation resource = clientResource.authorization().resources()
|
||||
|
@ -402,7 +404,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
clientResource.authorization().resources().resource(resource.getId()).remove();
|
||||
|
||||
httpFacade = createHttpFacade("/api/any-resource/test", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -412,7 +414,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resource/public");
|
||||
policyEnforcer.enforce(httpFacade);
|
||||
policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
TestResponse response = TestResponse.class.cast(httpFacade.getResponse());
|
||||
assertEquals(401, response.getStatus());
|
||||
}
|
||||
|
@ -461,7 +463,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/resource-with-scope", token);
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse("Should fail because resource does not have any scope named GET", context.isGranted());
|
||||
assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
|
||||
|
@ -473,18 +475,18 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-match-http-verbs-scopes.json"));
|
||||
policyEnforcer = deployment.getPolicyEnforcer();
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
// create a PATCH scope without associated it with the resource so that a PATCH request is denied accordingly even though
|
||||
// the scope exists on the server
|
||||
clientResource.authorization().scopes().create(new ScopeRepresentation("PATCH"));
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token, "PATCH");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
ScopePermissionRepresentation postPermission = new ScopePermissionRepresentation();
|
||||
|
@ -496,7 +498,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
permissions.scope().create(postPermission).close();
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
|
@ -511,11 +513,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
token = authorize.getToken();
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
|
@ -526,11 +528,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
token = authorize.getToken();
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
|
@ -541,11 +543,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
token = authorize.getToken();
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
|
@ -560,11 +562,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
token = authorize.getToken();
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token);
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -585,7 +587,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-bearer-only.json"));
|
||||
PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/check-subject-token");
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
|
||||
|
@ -600,7 +602,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
httpFacade = createHttpFacade("/api/check-subject-token", token);
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -632,12 +634,12 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
|
||||
httpFacade = createHttpFacade("/api/check-subject-token", token);
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(httpFacade);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
oauth.doLogout(response.getRefreshToken(), null);
|
||||
|
||||
context = policyEnforcer.enforce(httpFacade);
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
assertFalse(context.isGranted());
|
||||
}
|
||||
|
||||
|
@ -686,13 +688,15 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
String token = response.getAccessToken();
|
||||
|
||||
for (int i = 0; i < 101; i++) {
|
||||
policyEnforcer.enforce(createHttpFacade("/api/" + i, token));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/" + i, token);
|
||||
policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
}
|
||||
|
||||
assertEquals(101, policyEnforcer.getPathMatcher().getPathCache().size());
|
||||
|
||||
for (int i = 101; i < 200; i++) {
|
||||
policyEnforcer.enforce(createHttpFacade("/api/" + i, token));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/" + i, token);
|
||||
policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
}
|
||||
|
||||
assertEquals(200, policyEnforcer.getPathMatcher().getPathCache().size());
|
||||
|
@ -706,7 +710,8 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-lazyload-with-paths.json"));
|
||||
policyEnforcer = deployment.getPolicyEnforcer();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/0", token));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api/0", token);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
@ -738,7 +743,8 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
OAuthClient.AccessTokenResponse tokeResponse = oauth.doAccessTokenRequest(code, null);
|
||||
String token = tokeResponse.getAccessToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api-method/foo", token));
|
||||
OIDCHttpFacade httpFacade = createHttpFacade("/api-method/foo", token);
|
||||
AuthorizationContext context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
// GET is disabled in the config
|
||||
assertTrue(context.isGranted());
|
||||
|
@ -751,7 +757,8 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
|||
assertTrue(PolicyEnforcerConfig.ScopeEnforcementMode.DISABLED.equals(methods.get(0).getScopesEnforcementMode()));
|
||||
|
||||
// other verbs should be protected
|
||||
context = policyEnforcer.enforce(createHttpFacade("/api-method/foo", token, "POST"));
|
||||
httpFacade = createHttpFacade("/api-method/foo", token, "POST");
|
||||
context = policyEnforcer.enforce(new HttpAuthzRequest(httpFacade), new HttpAuthzResponse(httpFacade));
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
} finally {
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.junit.BeforeClass;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
|
@ -50,6 +49,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientSecretCredentialsProvider;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.apache.http.message.BasicNameValuePair;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
@ -52,6 +51,7 @@ import org.keycloak.keys.PublicKeyStorageUtils;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.jetbrains.annotations.NotNull;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
|
||||
import org.keycloak.common.Profile;
|
||||
|
@ -56,6 +55,7 @@ import org.keycloak.events.Details;
|
|||
import org.keycloak.models.ClientSecretConstants;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientSecretCredentialsProvider;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
|
||||
import org.keycloak.representations.idm.ClientProfilesRepresentation;
|
||||
|
|
|
@ -40,7 +40,6 @@ import org.junit.rules.TemporaryFolder;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
|
@ -69,6 +68,7 @@ import org.keycloak.jose.jws.JWSBuilder;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.KeyStoreConfig;
|
||||
|
|
|
@ -90,12 +90,12 @@
|
|||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"header-b": ["header-b-value1", "header-b-value2"],
|
||||
"Authorization": "Bearer {keycloak.id_token}"
|
||||
"Authorization": "Bearer {keycloak.access_token}"
|
||||
},
|
||||
"parameters": {
|
||||
"param-a": ["param-a-value1", "param-a-value2"],
|
||||
"param-subject": "{keycloak.id_token['/sub']}",
|
||||
"param-user-name": "{keycloak.id_token['/preferred_username']}"
|
||||
"param-subject": "{keycloak.access_token['/sub']}",
|
||||
"param-user-name": "{keycloak.access_token['/preferred_username']}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue